PageSourceSearch

https://drafthero.com/js/drafthero.js?version=1.1.7

js drafthero.com collected 2026-10-02 00:29:14 UTC 164,181 bytes, 4,406 lines download raw bytes

1//js files version
2const dhversion = Date.now();
3const imgpath = 'https://drafthero.blob.core.windows.net/$web/img/photo/';//10055.gif
4const browserExtensionLink = 'https://chrome.google.com/webstore/detail/draft-hero-live-sync-for/bedajlgghndfjocdbmhmkcdlpljaakad';
5
6//variables
7const currentSeason = 2026;
8var jsonPlayerListArr = [];
9var sourcesLoaded = 0;
10var sitesLoaded = 0;
11
12var savedTopRosters = [];
13var depthObj = {};
14var phone = false;
15var view = '';
16
17var selections = { 'default': { 'Pos': 'QB', 'Source': 'Demo' }, 'user': { 'Pos': 'QB', 'Source': 'Demo' } };
18
19var extensionInstalled = false;
20
21//HTML saved for UI
22var savedObj = {};
23
24//Team defense rankings from source
25var defenseRankingsArr = [];
26
27//User preferences
28var userPreferences = {};
29//Core Analytics
30function AnalyticsObj() {
31    this.BaselineValuesObj = {};
32    this.BestAvailableByPick = {};
33    this.DynamicBaselineValuesObj = {};
34    this.PositionRankingsObj = { 'QB': {}, 'RB': {}, 'WR': {}, 'TE': {}, 'DT': {}, 'PK': {} };
35    this.IDPPositionRankingsObj = { 'All': {}, 'DE': {}, 'DL': {}, 'LB': {}, 'CB': {}, 'S': {} };
36    this.DraftValue = {};
37    this.TopRecommendedArr = new Array(5);
38    this.MyPointsByPos = {
39        Starters: { 'All': 0, 'QB': 0, 'RB': 0, 'WR': 0, 'TE': 0, 'DT': 0, 'PK': 0, 'Flex': 0 },
40        Bench: { 'All': 0, 'QB': 0, 'RB': 0, 'WR': 0, 'TE': 0, 'DT': 0, 'PK': 0, 'Flex': 0 },
41        Total: 0
42    };
43}
44var analyticsObj = {};
45//NFL Variables
46var byeWeeks = {
47    "ARZ": 14,
48    "ATL": 11,
49    "BAL": 13,
50    "BUF": 7,
51    "CAR": 5,
52    "CHI": 10,
53    "CIN": 6,
54    "CLE": 11,
55    "DAL": 14,
56    "DEN": 10,
57    "DET": 6,
58    "GB": 11,
59    "HOU": 8,
60    "IND": 13,
61    "JAC": 7,
62    "KC": 5,
63    "LA": 11,
64    "LAC": 7,
65    "LV": 13,
66    "MIA": 6,
67    "MIN": 6,
68    "NE": 11,
69    "NO": 8,
70    "NYG": 8,
71    "NYJ": 13,
72    "PHI": 10,
73    "PIT": 9,
74    "SEA": 11,
75    "SF": 8,
76    "TB": 10,
77    "TEN": 9,
78    "WAS": 7
79};
80
81byeWeeks['FA'] = '-';
82var contestMap = new Map();
83contestMap.set('FFPC', ['Main Event', 'FantasyPros Championship', 'Best Ball Tournament', 'Best Ball Leagues', 'Best Ball Slim', 'Superflex Classic', 'Superflex Best Ball', 'Superflex Best Ball Slim', 'FFPC Classic', 'FFPC Dynasty', 'Pros vs Joes']);
84contestMap.set('NFFC', ['Primetime', 'Classic', 'RotoWire Online', 'Footballguys Online', 'Silver Bullet', 'Be
84st Ball Live', 'Footballguys Best Ball Overall', 'Best Ball Double Up', 'BB10']);
85contestMap.set('RTSports', ['Commissioner Service', 'Fantasy Life Championship', 'Best Ball Championship']);
86contestMap.set('Underdog', ['Best Ball Mania', 'The Marathon', 'The Big Dog', 'The Husky', 'The Puppy', 'Badge Bros Brawl', 'MMG Best Ball']);
87//leagues
88var leagueList = {};
89var leagueSelected = null;
90var settings = {};
91var leagueSynced = false;
92
93function defaultSettings() {
94    this.General = {
95        'Name': 'Demo League 2024',
96        'Abbrev': 'DEMO24',
97        'DateCreated': '',
98        'DateUpdated': '',
99        'Status': 'Created'
100    };
101    this.MyTeam = {
102        'Name': 'Team 1',
103        'ID': '1',
104        'DraftPosition': 1
105    };
106    this.Draft = {
107        'Style': 'Snake',
108        'Rounds': 15,
109        'Order': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
110        'OrderByTeamID': { '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, '11': 11, '12': 12 },
111        'Date': '',
112        'Type': ''
113    };
114    this.Rosters = {
115        'Size': 15,
116        'Starters': 9,
117        'Bench': 6,
118        'Pos': {
119            'QB': { 'Starters': 1, 'Bench': 0 },
120            'RB': { 'Starters': 2, 'Bench': 2 },
121            'WR': { 'Starters': 2, 'Bench': 2 },
122            'TE': { 'Starters': 1, 'Bench': 0 },
123            'DT': { 'Starters': 1, 'Bench': 0 },
124            'PK': { 'Starters': 1, 'Bench': 0 },
125            'BE': { 'Starters': 0, 'Bench': 2 }
126        },
127        'Flex': { 'RB-WR-TE': { 'Starters': 1, 'Bench': 0 } }
128    };
129    this.Scoring = {
130        'All': { 'PAYD': 0.04, 'PATD': 4, 'PAINT': -1, 'RUYD': 0.1, 'RUTD': 6, 'REC': 0.5, 'REYD': 0.1, 'RETD': 6, 'FUML': -2, 'CONV': 2, 'FG': 3, 'FG40': 1, 'FG50': 2, 'PAT': 1, 'SF': 1, 'INT': 2, 'FR': 2, 'DTD': 6, 'INTTD': 6, 'FRTD': 6, 'STD': 6, 'PA0': 10, 'PA10': 7, 'PA7': 4, 'PA14': 1, 'PA18': -1, 'PA28': -4, 'PA35': -4, 'PA46': -4 },
131        'Positional': {}
132    };
133    this.Teams = {
134        'Size': 12,
135        'Names': {
136            '1': 'Team 1', '2': 'Team 2', '3': 'Team 3', '4': 'Team 4', '5': 'Team 5', '6': 'Team 6', '7': 'Team 7', '8': 'Team 8', '9': 'Team 9', '10': 'Team 10', '11': 'Team 11', '12': 'Team 12'
137        }
138    };
139    this.Host = {
140        'Site': 'Yahoo',
141        'SiteTemplate': 'Demo',
142        'SiteID': '',
143        'Synced': false
144    };
145    this.Options = {};
146}
147
148//Players Objects
149function PlayerObj(first, last, pos, team, bye) {//infoArr
150    this.Bye = bye;
151    this.NameObj = {
152        Full: first + ' ' + last,
153        First: first,
154        Last: last,
155        Short: first.substring(0, 1) + '. ' + last,
156        str: first.toLowerCase() + '-' + last.toLowerCase() + '-' + pos.toLowerCase()
157    };
158    this.Pos = pos;
159    this.Team = team;
160}
161var playersObj = {};
162var playersCards = {};//HTML for player card
163var playersNames = {};//Most common display name based on device size
164var playersLastNames = {};
165var playersShortNames = {};
166var playersStats = {};
167var playersStatsBySource = {};
168var playersDrafted = {};
169
170var playersPoints = {};
171var playersPointsBySource = {};
172
173//Average draft position
174var playersADP = {};
175var playersADPBySite = {};
176var playersRankedByADP = [];
177var siteADP = {};
178
179//Average auction values
180var playersAAV = {};
181var playersAAVBySite = {};
182var playersRankedByAAV = [];
183var siteAAV = {};
184
185var playersARV = {};
186var playerTiers = {};
187var playerSearchArr = [];
188
189var playersRankings = [];//array of playerids in order of ranking by points
190var playersRankingsBySource = {};
191
192var playersPosRanking = {};//playerID:PosRank
193var playersPosRankingBySource = {};
194
195var posRankings = {};//Pos:[playerids]
196var posRankingsBySource = {};// Source[pos]:[playerids]
197
198var playerNews = {};
199var playerNotes = {
200    "4for4": {}, "FantasyGuru": {}, "RotoWire": {} };
201
202var loadedStatsBySource = {};
203
204var handcuffsObj = {};
205
206var playersDepth = {};//players depth chart on roster
207
208//IDP
209var idpRankingByPosition = {} ;
210var idpRankedByPosition = {};
211var idpRankedByADP = [];
212//User Options
213var selectedSource = 'Blended';
214//positions
215var posListArr = ['QB', 'RB', 'WR', 'TE', 'PK', 'DT'];
216var allPositions = { 'Pos': ['QB', 'RB', 'WR', 'TE', 'PK', 'DT'], 'Flex': ['RB-WR-T
216E', 'RB-WR', 'WR-TE', 'QB-RB-WR-TE'] };
217var idpPosListArr = ['DE','DL','LB','CB','S'];
218var allPositionsOrder = ['QB', 'RB', 'WR', 'TE', 'RB-WR-TE', 'RB-WR', 'WR-TE', 'QB-RB-WR-TE', 'PK', 'DT'];
219var posLabels = { 'QB': 'QB', 'RB': 'RB', 'WR': 'WR', 'TE': 'TE', 'RB-WR-TE': 'RWT', 'RB-WR': 'RW', 'WR-TE': 'WT', 'QB-RB-WR-TE': 'SF', 'PK': 'PK', 'DT': 'DT', 'DP': 'DP' };
220
221//variables
222var statCategories = {//Demo used for blend and should include all categories
223    OffenseArr: {
224        'Demo': ['ID', 'PAATT', 'PACMP', 'PAINC', 'PAYD', 'PATD', 'PAINT', 'CONV', 'PaCmp40', 'PaTD40', 'PAFD', 'SACKED', 'RUATT', 'RUYD', 'RUTD', 'RuConv', 'RuAtt40', 'RuTD40', 'RUFD', 'REC', 'REYD', 'RETD', 'ReConv', 'Recpt40', 'ReTD40', 'TARGET', 'REFD', 'FUM', 'FUML', 'KRTD', 'KRYD', 'PRTD', 'PRYD'],
225        'Inseason': ['ID', 'PAINC', 'PACMP', 'PAATT', 'PAYD', 'PATD', 'PAINT', 'SACKED', 'RUATT', 'RUYD', 'RUTD', 'REC', 'REYD', 'RETD', 'FUML', 'PAFD', 'RUFD', 'REFD'],
226        '4for4': ['ID', 'PAINC', 'PACMP', 'PAATT', 'PAYD', 'PATD', 'PAINT', 'SACKED', 'RUATT', 'RUYD', 'RUTD', 'REC', 'REYD', 'RETD', 'FUML', 'PAFD', 'RUFD', 'REFD'],
227        'FantasyGuru': ['ID', 'PAATT', 'PACMP', 'PAINC', 'PAYD', 'PATD', 'PAINT', 'RUATT', 'RUYD', 'RUTD', 'REC', 'REYD', 'RETD', 'FUML'],
228        'Ultimate': ['ID', 'PAINC', 'PAATT', 'PACMP', 'PAYD', 'PATD', 'PAINT', 'SACKED', 'RUATT', 'RUYD', 'RUTD', 'TARGETS', 'REC', 'REYD', 'RETD', 'FUML', 'PATD50', 'PAY300', 'PAFD', 'RUTD50', 'RUY100', 'RUFD', 'RETD50', 'REY100', 'REFD', 'PRYD', 'PRTD'],
229        'RotoWire': ['ID', 'PAATT', 'PACMP', 'PAINC', 'PAYD', 'PATD', 'PAINT', 'CONV', 'PATD50', 'PAFD', 'SACKED', 'RUATT', 'RUYD', 'RUTD', 'CONV', 'RUTD50', 'RUFD', 'REC', 'REYD', 'RETD', 'CONV', 'RETD50', 'TARGET', 'REFD', 'FUM', 'FUML', 'KRTD', 'KRYD', 'PRTD', 'PRYD'],
230        'Actual': ['ID', 'PAATT', 'PACMP', 'PAYD', 'PATD', 'PAINT', 'RUATT', 'RUYD', 'RUTD', 'TARGET', 'REC', 'REYD', 'RETD', 'FUML']//, 'AVG', 'TOTAL'
231    },
232    DefenseArr: {
233        //'Demo': ['ID', 'Rank', 'FR', 'DTD', 'SK', 'PAA', 'FPTS'],
234        'Demo': ['ID', 'SK', 'SF', 'INT', 'FR', 'DTD', 'PAA', 'FPTS', 'Rank'],
235        'Inseason': ['ID', 'SK', 'SF', 'INT', 'FR', 'DTD', 'PAA', 'FPTS'],
236        '4for4': ['ID', 'FR', 'DTD', 'SK', 'INT', 'PAA', 'FPTS','Rank'],
237        'FantasyGuru': ['ID', 'Rank', 'FR', 'DTD', 'SK', 'PAA', 'FPTS'],
238        'Ultimate': ['ID', 'SK', 'SF', 'INT', 'FR', 'DTD', 'PAA', 'FPTS'],
239        'RotoWire': ['ID', 'FR', 'DTD', 'SK', 'INT', 'PAA', 'FPTS'],
240        'Actual': ['ID', 'FPTS']
241    },
242    KickersArr: {
243        'Demo': ['ID', 'FGA', 'FG', 'PATATT', 'PAT', 'PATM', 'FG0_19', 'FG20', 'FG30', 'FG40', 'FG50', 'FGM0_19', 'FGM20', 'FGM30', 'FGM40', 'FGM50'],
244        'Inseason': ['ID', 'FGA', 'FG', 'PATATT', 'PAT', 'PATM', 'FG0_19', 'FG20', 'FG30', 'FG40', 'FG50', 'FGM0_19', 'FGM20', 'FGM30', 'FGM40', 'FGM50'],
245        '4for4': ['ID', 'FG', 'PAT'],
246        'FantasyGuru': ['ID', 'FG', 'FGA', 'FGM', 'PAT', 'PATATT', 'PATM', 'FG0_19', 'FG20', 'FG30', 'FG40', 'FG50'],
247        'Ultimate': ['ID', 'FG', 'FGA', 'FGM', 'PAT', 'PATATT', 'PATM', 'FG0_19', 'FG20', 'FG30', 'FG40', 'FG50', 'FGM0', 'FGM20', 'FGM30', 'FGM40', 'FGM50'],
248        'RotoWire': ['ID', 'FGA', 'FG', 'PATATT', 'PAT', 'PATM', 'FG0_19', 'FG20', 'FG30', 'FG40', 'FG50', 'FGM0', 'FGM20', 'FGM30', 'FGM40', 'FGM50'],
249        'Actual': ['ID', 'FG', 'FG20', 'FG30', 'FG40', 'FG50', 'PAT']
250    }
251};
252var allStats = {
253    'Offense': ['PAATT', 'PACMP', 'PAINC', 'PAYD', 'PATD', 'PAINT', 'PAFD', 'PAY300', 'PAY400', 'PATD50', 'SACKED', 'RUATT', 'RUYD', 'RUTD', 'RUFD', 'RUY100', 'RUY200', 'RUTD50', 'REC', 'REC-RB', 'REC-WR', 'REC-TE', 'REYD', 'RETD', 'TARGET', 'REFD', 'REFD-RB', 'REFD-WR', 'REFD-TE', 'REY100', 'REY200', 'RETD50', 'FUM', 'FUML', 'CONV', 'RETURN', 'KRTD', 'KRYD', 'PRTD', 'PRYD'],
254    'Defense': ['STKRTD', 'STKRYD', 'STPRTD', 'STPRTYD', 'KRPRTD', 'INTTD', 'FRTD', 'BLKKRTD', 'DTD', 'STD', 'SK', 'BLKK', 'INT', 'FF', 'FR', 'SF', 'PA0', 'PA1', 'PA7', 'PA14', 'PA18', 'PA28', 'PA35', 'PA46', 'PA55', 'PA10', 'PA20', 'PA30', 'YA100', 'YA150', 'YA199', 'YA299', 'YA399', 'YA449', 'YA499', 'YA549', 'YA550'],
255    'Kickers': ['FGA', 'FG', 'FG0', 'FG0_19', 'FG20', 'FG30', 'FG40', 'FG50', 'FG60', 'FGM', 'FGM0', 'FGM0_19', 'FGM20', 'FGM30', 'FGM40', 'FGM50', 'FGM60', 'PATATT', 'PAT', 'PATM']
256}
257var histStats = { 'posRank': {}, 'posRankingsWeekly': {}, 'posRankingsSummary': {}, 'seasonStats': {}, 'baseline': {} };
258//Baseline stats for prior years
259histStats.baseline['2025'] = {//TO DO update these for 2022
260    "QB": [3803, 33, 10, 11, 0, 0, 0, 0],
261    "RB": [0, 0, 0, 365, 1, 80, 589, 2],
262    "WR": [74, 1, 0, 10, 1, 72, 840, 3],
263    "TE": [0, 0, 0, 0, 0, 50, 456, 8]
264};
265histStats.baseline['2024'] = {//TO DO update these for 2022
266    "QB": [3803, 33, 10, 11, 0, 0, 0, 0],
267    "RB": [0, 0, 0, 365, 1, 80, 589, 2],
268    "WR": [74, 1, 0, 10, 1, 72, 840, 3],
269    "TE": [0, 0, 0, 0, 0, 50, 456, 8]
270};
271histStats.baseline['2023'] = {//TO DO update these for 2022
272    "QB": [3803, 33, 10, 11, 0, 0, 0, 0],
273    "RB": [0, 0, 0, 365, 1, 80, 589, 2],
274    "WR": [74, 1, 0, 10, 1, 72, 840, 3],
275    "TE": [0, 0, 0, 0, 0, 50, 456, 8]
276};
277var nflRostersRankObj = {
278    'rankOrderArr': ['DAL', 'TB', 'BUF', 'KC', 'LAC', 'NE', 'CIN', 'LA', 'IND', 'GB', 'ARZ', 'PHI', 'SF', 'MIN', 'TEN', 'SEA', 'BAL', 'LV', 'NO', 'CLE', 'PIT', 'MIA', 'DEN', 'WAS', 'DET', 'ATL', 'CHI', 'NYJ', 'CAR', 'HOU', 'NYG', 'JAC'],
279    'alphaOrderArr': ['ARZ', 'ATL', 'BAL', 'BUF', 'CAR', 'CHI', 'CIN', 'CLE', 'DAL', 'DEN', 'DET', 'GB', 'HOU', 'IND', 'JAC', 'KC', 'LAC', 'LA', 'LV', 'MIA', 'MIN', 'NE', 'NO', 'NYG', 'NYJ', 'PHI', 'PIT', 'SEA', 'SF', 'TB', 'TEN', 'WAS']
280};
281
282var statDescriptions = {
283    'PAATT': 'Passing Attempts',
284    'PACMP': 'Passing Completions',
285    'PAINC': 'Passing Incompletions',
286    'PAYD': 'Passing Yards',
287    'PATD': 'Passing TD',
288    'PAINT': 'Interceptions Thrown',
289    'PAFD': 'Passing First Down',
290    'PAY300': 'Passing Game 300-399 Yards',
291    'PAY400': 'Passing Game 400+ Yards',
292    'PATD50': 'Passing TD 50+ Yards',
293    'SACKED': 'Sacked',
294    'RUATT': 'Rushing Attempts',
295    'RUYD': 'Rushing Yards',
296    'RUTD': 'Rushing TD',
297    'RUFD': 'Rushing First Down',
298    'RUY100': 'Rushing Game 100-199 Yards',
299    'RUY200': 'Rushing Game 200+ Yards',
300    'RUTD50': 'Rushing TD 50+ Yards',
301    'REC': 'Reception (All Positions)',
302    'REC-RB': 'Reception by RB',
303    'REC-WR': 'Reception by WR',
304    'REC-TE': 'Reception by TE',
305    'REYD': 'Receiving Yards',
306    'RETD': 'Receiving TD',
307    'TARGET': 'Receiving Target',
308    'REFD': 'Receiving First Down',
309    'REFD-RB': 'Receiving First Down by RB',
310    'REFD-WR': 'Receiving First Down by WR',
311    'REFD-TE': 'Receiving First Down by TE',
312    'REY100': 'Receiving Game 100-199 Yards',
313    'REY200': 'Receiving Game 200+ Yards',
314    'RETD50': 'Receiving TD 50+ Yards',
315    'FUM': 'Total Fumbles',
316    'FUML': 'Total Fumbles Lost',
317    'CONV': 'Conversion (Any)',
318    'RETURN': 'Kickoff Return TD OR Punt Return TD',
319    'KRTD': 'Kickoff Return TD',
320    'KRYD': 'Kickoff Return Yards',
321    'PRTD': 'Punt Return TD',
322    'PRYD': 'Punt Return Yards',
323    'STKRTD': 'Kickoff Return TD',
324    'STKRYD': 'Kickoff Return Yards',
325    'STPRTD': 'Punt Return TD',
326    'STPRTYD': 'Punt Return Yards',
327    'KRPRTD': 'Kickoff Return TD OR Punt Return TD',
328    'INTTD': 'Interception Return TD',
329    'FRTD': 'Fumble Return TD',
330    'BLKKRTD': 'Blocked Punt or FG return for TD',
331    'DTD': 'Defensive TD',
332    'STD': 'Special Teams TD',
333    'SK': 'Each Sack',
334    'SKYD': 'Each Sack Yard',
335    'BLKK': 'Blocked Punt, PAT or FG',
336    'INT': 'Each Interception',
337    'FF': 'Each Fumble Forced',
338    'FR': 'Each Fumble Recovered',
339    'SF': 'Each Safety',
340    'PA0': '0 points allowed',
341    'PA1': '1-6 points allowed',
342    'PA7': '7-13 points allowed',
343    'PA14': '14-17 points allowed',
344    'PA18': '18-27 points allowed',
345    'PA28': '28-34 points allowed',
346    'PA35': '35-45 points allowed',
347    'PA46': '46 + points allowed',
348    'PA10': '10-19 points allowed',
349    'PA20': '20-29 points allowed',
350    'PA30': '30+ points allowed',
351    'YA100': 'Less than 100 total yards allowed',
352    'YA150': 'Less than 150 total yards allowed',
353    'YA199': '100-199 total yards allowed',
354    'YA299': '200-299 total yards allowed',
355    'YA399': '300-399 total yards allowed',
356    'YA449': '400-449 total yards allowed',
357    'YA499': '450-499 total yards allowed',
358    'YA549': '500-549 total yards allowed',
359    'YA550': '550 + total yards allowed',
360    'FGA': 'FG Attempt',
361    'FG': 'FG Made',
362    'FG0': 'FG Made 0-39 yards',
363    'FG0_19': 'FG Made 0-19 yards',
364    'FG20': 'FG Made 20-29 yards',
365    'FG30': 'FG Made 30-39 yards',
366    'FG40': 'FG Made 40-49 yards',
367    'FG50': 'FG Made 50-59 yards',
368    'FG60': 'FG Made 60 + yards',
369    'FGM': 'Total FG Missed',
370    'FGM0': 'FG Missed 0-39 yards',
371    'FGM0_19': 'FG Missed 0-19 yards',
372    'FGM20': 'FG Missed 20-29 yards',
373    'FGM30': 'FG Missed 30-39 yards',
374    'FGM40': 'FG Missed 40-49 yards',
375    'FGM50': 'FG Missed 50-59 yards',
376    'FGM60': 'FG Missed 60+ yards',
377    'PATATT': 'PAT Attempt',
378    'PAT': 'PAT Made',
379    'PATM': 'PAT Missed',
380    'FPTS': 'Fantasy Points'
381};
382//IDP
383//"IDP": '["Sacks", "SackYards",
384//"SoloTackles", "AssistedTackles", "TotalTackles", "TacklesForLoss",
385//"QuarterbackHits",
386//"FumblesForced", "FumblesRecovered", "FumbleReturnTouchdowns",
387//"Interceptions", "InterceptionReturnTouchdowns",
388//"PassesDefended", "Safeties"]'
389//subscription
390var subscription = {
391    'current': 'Demo,Yahoo',
392    'apps': [],
393    'projections': ['Demo'],
394    'sites': ['Yahoo'],
395    'status': 'Demo'
396};
397var subscriptionOptions = {
398    'apps': ['Draft', 'Auction', 'Lineup'],
399    'projections': ['4for4', 'FantasyGuru', 'RotoBaller', 'Ultimate', 'RotoWire'],
400    'sites': ['Yahoo', 'ESPN', 'CBS', 'NFL', 'Sleeper', 'MFL', 'RTSports' ],
401    'contests': ['TFC', 'FFPC', 'NFFC', 'Underdog']
402};
403var user = { 'email': null, 'status': 'Demo' };
404var discountPercent = null;
405var discountAmount = null;
406//Teams
407var teamsObj = new TeamsObj();
408function TeamsObj() {
409    this.AnalyticsObj = {};
410    this.LeagueSummary = {};
411    this.KeepersPlayerIDsObj = {};//Array of PlayerIDs
412    this.RostersPlayerIDsArr = {};//Object of Array
413    this.RostersPlayerPointsObj = {};//Object of Array
414    this.RostersSummaryObj = { Drafted: {}, All: {} };
415    //this.RostersFlexSummaryObj = { Drafted: {}, All: {} };
416    this.RostersPointsObj = {};
417    this.String;//for drop down menus
418}
419
420var currentWeek = 1;
421function setCookie(cname, cvalue) {
422    var d = new Date();
423    d.setTime(d.getTime() + 90 * 24 * 60 * 60 * 1000);
424    var expires = "expires=" + d.toUTCString();
425    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
426}
427
428function getCookie(cname) {
429    var name = cname + "=";
430    var ca = document.cookie.split(';');
431    for (var i = 0; i < ca.length; i++) {
432        var c = ca[i];
433        while (c.charAt(0) === ' ') {
434            c = c.substring(1);
435        }
436        if (c.indexOf(name) === 0) {
437            return c.substring(name.length, c.length);
438        }
439    }
440    return "";
441}
442
443function deleteCookie(cname) {
444    document.cookie = cname + "=''; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
445}
446
447function deleteAllCookies() {
448    document.cookie.split(';').forEach(function (c) {
449        document.cookie = c.trim().split('=')[0] + '=;' + 'expires=Thu, 01 Jan 1970 00:00:00 UTC;';
450    });
451}
452function dateSince(date) {
453    var now = new Date();
454    var difference = Math.abs(now.getTime() - date.getTime()) / 1000;
455    var str = '';
456    //var dif = [60, 3600, 86400, 604800, 1209600, 1814400, 2419200, 5184000, 31536000, 63072000]
457    
458    switch (true) {
459        case difference < 60:
460            str = ~~difference + ' sec ago';
461            break;
462        case difference < 3600:
463            str = ~~(difference/60) + ' min ago';
464            break;
465        case difference < 86400://1 day
466            str = ~~(difference / 3600) + ' hrs ago';
467            break;
468        case difference < 604800://7 days
469            str = ~~(difference / 86400) + ' days ago';
470            break;
471        case difference < 1209600://14 days
472            str = 'Last week';
473            break;
474        case difference < 1814400://21 days
475            str = 'Two weeks ago';
476            break;
477        case difference < 2419200://28 days
478            str = 'Three weeks ago';
479            break;
480        case difference < 5184000://60 days
481            str = 'A month ago';
482            break;
483        case difference < 31536000://365 days
484            str = ~~(difference / 2592000) + ' months ago';
485            break;
486        case difference < 63072000://730 days
487            str = 'Last year';
488            break;
489        default:
490            str = 'A long time ago';
491            break;
492    }
493    return str;
494}
495function convertDate(date) {
496    date = new Date(date);
497    var newdate = (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear();
498    return newdate;
499}
500function getCurrentDate() {
501    var date = new Date();
502    var month = date.getMonth() + 1;
503    str = month + '/' + date.getDate() + '/' + date.getFullYear();
504    return str;
505}
506function getPastDate(days) {
507    var date = new Date();
508    date.setDate(date.getDate() - days);
509    var month = date.getMonth() + 1;
510    str = month + '/' + date.getDate() + '/' + date.getFullYear();
511    return str;
512}
513/*****************Notes**************************************************************
514    Controllers for drag and drop
515    https://github.com/bradtraversy/vanillawebprojects/tree/master/sortable-list
516    https://www.youtube.com/watch?v=wv7pvH1O5Ho
517************************************************************************************/
518
519// Store listitems
520var draggableListItems = [];
521let dragStartIndex;
522
523function dragStart() {
524    // console.log('Event: ', 'dragstart');
525    dragStartIndex = +this.closest('li').getAttribute('data-index');
526}
527
528function dragEnter() {
529    // console.log('Event: ', 'dragenter');
530    this.classList.add('over');
531}
532
533function dragLeave() {
534    // console.log('Event: ', 'dragleave');
535    this.classList.remove('over');
536}
537
538function dragOver(e) {
539    // console.log('Event: ', 'dragover');
540    e.preventDefault();
541}
542
543function dragDrop() {
544    // console.log('Event: ', 'drop');
545    const dragEndIndex = +this.getAttribute('data-index');
546    swapItems(dragStartIndex, dragEndIndex);
547
548    this.classList.remove('over');
549}
550
551// Swap list items that are drag and drop
552function swapItems(fromIndex, toIndex) {
553    const itemOne = draggableListItems[fromIndex].querySelector('.draggable');
554    const itemTwo = draggableListItems[toIndex].querySelector('.draggable');
555
556    draggableListItems[toIndex].appendChild(itemOne);
557    draggableListItems[fromIndex].appendChild(itemTwo);
558    updateDraftOrder();
559}
560
561function addDraggableEventListeners() {
562    const draggables = document.querySelectorAll('.draggable');
563    const dragListItems = document.querySelectorAll('.draggable-list li');
564
565    draggables.forEach(draggable => {
566        draggable.addEventListener('dragstart', dragStart);
567    });
568
569    dragListItems.forEach(item => {
570        item.addEventListener('dragover', dragOver);
571        item.addEventListener('drop', dragDrop);
572        item.addEventListener('dragenter', dragEnter);
573        item.addEventListener('dragleave', dragLeave);
574    });
575}
576window.onerror = function (msg, url, lineNo, columnNo, error) {
577    var email = user.email || 'na';
578    var obj = {
579        from: email,
580        subject: 'window.onerror',
581        msg: msg + ' | url: ' + url + ' | lineNo: ' + lineNo + ' | columnNo: ' + columnNo + ' | error: ' + error
582    };
583    console.log(obj);
584    //sendEmailtoSupport(obj);
585};
586
587function logError(msg, url, section, subsection, error) {
588    if (msg.indexOf("playerNotes") > -1) return;
589    if (url.indexOf("sync") === -1) return;
590    var str = '';
591    var stackArr = error.split('\n');
592    for (var i = 1; i < stackArr.length; i++) {
593        str += ' | ' + stackArr[i].split('at')[1].split(' ')[1].replace(/[&\/\\#+$~%'"*?<>{}]/g, '_');
594    }
595
596    var obj = {
597        from: user.email,
598        subject: 'window.onerror' + leagueSelected,
599        msg: msg + ' | url: ' + url.replace(/[&\/\\#+$~%'"*?<>{}]/g, '_') + ' | section: ' + section + ' | subsection: ' + subsection + ' | error: ' + str
600    };
601    console.log(obj);
602    //sendEmailtoSupport(obj);
603
604}
605function sendEmailtoSupport(obj) {
606    post(obj, '/sendEmailtoSupport', function (result) {
607        console.log(result);
608    });
609}
610function addEventbyClassName(cn, type, event) {//classname, "click", function to call
611    var elements = document.getElementsByClassName(cn);
612    for (var i = 0; i < elements.length; i++) {
613        elements[i].addEventListener(type, event);
614    }
615}
616function addEventbyID(id, type, event) {//element id, "click", function to call
617    var element = document.getElementById(id);
618    element.addEventListener(type, event);
619}
620function addEventToElements(elements, type, event) {//classname, "click", function to call
621    for (var i = 0; i < elements.length; i++) {
622        elements[i].addEventListener(type, event);
623    }
624}
625function updateHTMLbyClassName(cn, html) {
626    var elements = document.getElementsByClassName(cn);
627    for (var i = 0; i < elements.length; i++) {
628        elements[i].innerHTML = html;
629    }
630}
631function addClassbyClassName(cn, cl) {//classname add is-hidden
632    var elements = document.getElementsByClassName(cn);
633    for (var i = 0; i < elements.length; i++) {
634        elements[i].classList.add(cl);
635    }
636}
637function removeClassbyClassName(cn, cl) {//classname remove is-hidden
638    var elements = document.getElementsByClassName(cn);
639    for (var i = 0; i < elements.length; i++) {
640        elements[i].classList.remove(cl);
641    }
642}
643
644function checkExtension() {
645    //load extension.js if chrome extension is installed
646    try {
647        if (chrome.runtime) {
648            extensionInstalled = true;            
649            if (!isMobile && document.getElementById('msgExtensionInstalled')) document.getElementById('msgExtensionInstalled').style.display = 'block';
650        } else {
651            if (!isMobile && document.getElementById('msgExtension')) document.getElementById('msgExtension').style.display = 'block';
652        }
653    } catch (error) {
654        if (!isMobile && document.getElementById('msgExtension')) document.getElementById('msgExtension').style.display = 'block';
655        extensionInstalled = false;
656        console.error(error);
657    }
658}
659//************* CSV File Upload *************
660//Drag and Drop code
661function dropHandler(event) {
662    event.stopPropagation();
663    event.preventDefault();
664
665    if (event.dataTransfer.items) {
666        if (event.dataTransfer.items[0].kind === 'file') {
667            var file = event.dataTransfer.items[0].getAsFile();
668            console.log('... file[0].name = ' + file.name);
669
670            var reader = new FileReader();
671            reader.onload = function (event) {
672                var contents = event.target.result;
673                var lines = contents.split(/[\r\n]+/g);
674                var row = [], cell = null, arr = [];
675                for (var i = 0; i < lines.length; i++) {
676                    row = lines[i].split(',');
677                    arr.push(lines[i]);
678                }
679                mapPlayers(arr);
680            };
681            reader.readAsText(file);//Required for above code to run
682        }
683    }
684    removeDragData(event);
685}
686function dragOverHandler(event) {
687    event.preventDefault();
688}
689function removeDragData(event) {
690    if (event.dataTransfer.items) {
691        event.dataTransfer.items.clear();
692    } else {
693        event.dataTransfer.clearData();
694    }
695}
696function handleFileSelection(event) {
697    var files = event.target.files;
698    if (files.length > 0) {
699        var file = files[0];
700        if (file.type === "text/csv") {
701            console.log('Selected file:', file.name);
702            var reader = new FileReader();
703            reader.onload = function (event) {
704                var contents = event.target.result;
705                var lines = contents.split(/[\r\n]+/g);
706                var row = [], cell = null, arr = [];
707                for (var i = 0; i < lines.length; i++) {
708                    row = lines[i].split(',');
709                    arr.push(lines[i]);
710                }
711                mapPlayers(arr);
712            };
713            reader.readAsText(file);//Required for above code to run
714        } else {
715            alert("Please select a valid .csv file!");
716        }
717    }
718}
719//End of Drag and Drop code
720//************* END of CSV File Upload *************
721
722function exportProjections() {
723    var playerRankingsArr = playersRankingsBySource[selectedSource];
724    var playerID = null, playerObj = {};
725    var data = [];
726
727    if (settings.Draft.Style !== 'Auction') {
728        data.push('Name,Team,Pos,Points,VBD,ADP,PosRank');
729        for (var i = 0; i < playerRankingsArr.length; i++) {
730            playerID = playerRankingsArr[i];
731            playerObj = playersObj[playerID];
732            data.push(playerObj.NameObj.Full + ',' + playerObj.Team + ',' + playerObj.Pos + ',' + playersPoints[playerID] + ',' + playersARV[playerID] + ',' + playersADP[playerID] + ',' + playersPosRanking[playerID]);
733        }
734    } else {
735        data.push('Name,Team,Pos,Points,ARV,Bid,AAV,PosRank');
736        for (i = 0; i < playerRankingsArr.length; i++) {
737            playerID = playerRankingsArr[i];
738            playerObj = playersObj[playerID];
739            data.push(playerObj.NameObj.Full + ',' + playerObj.Team + ',' + playerObj.Pos + ',' + playerObj.PointsObj[selectedSource] + ',' + playerObj.AnalyticsObj.VBD.Starters[selectedSource] + ',' + playerObj.DollarValuesObj.DV + ',' + playerObj.DollarValuesObj.AAV + ',' + playerObj.AnalyticsObj.PosRank[selectedSource]);
740        }
741    }
742    exportArrToCSV(data, settings.General.Name + '.csv');
743}
744function exportTemplate() {
745    var playerRankingsArr = playersRankingsBySource[selectedSource];
746    var playerID = null, playerObj = {}, adp = 0;
747    var data = [];
748
749    data.push('Name,Team,Pos,Points,ADP');
750    for (var i = 0; i < playerRankingsArr.length; i++) {
751        playerID = playerRankingsArr[i];
752        playerObj = playersObj[playerID];
753        adp = playersADP[playerID] || 'na';
754        data.push(playerObj.NameObj.Full + ',' + playerObj.Team + ',' + playerObj.Pos + ',' + playersPoints[playerID] + ',' + adp);
755    }
756    exportArrToCSV(data, 'Draft Hero Template.csv');
757}
758function exportDraftResults() {
759    var playerRankingsArr = playersRankingObj[selectedSource];
760    var teamsObj = leagueObj.TeamsObj;
761    var playerID = null, draftPickObj = {}, playerObj = {};
762    var data = [];
763
764    //Headers
765    data.push('Round,Pick,Team,Name,Team,Pos,Points');
766    for (var i = 0, keys = Object.keys(draftPicksObj); i < keys.length; i++) {
767        draftPickObj = draftPicksObj[keys[i]];
768        playerObj = playersObj[draftPickObj.PlayerID];
769        if (playerObj) {
770            data.push(draftPickObj.Round + ',' + keys[i] + ',' + teamsObj[draftPickObj.TeamID] + ',' + playerObj.NameObj.Full + ',' + playerObj.Team + ',' + playerObj.Pos + ',' + playerObj.PointsObj.Projected);
771        } else {
772            data.push(draftPickObj.Round + ',' + keys[i] + ',' + teamsObj[draftPickObj.TeamID] + ',,,');
773        }
774    }
775    exportArrToCSV(data, settings.General.Name + ' Draft Results.csv');
776}
777
778function exportArrToCSV(arr, filename) {
779    var csvContent = arr.join("\r");
780    var a = document.createElement('a');
781    a.href = 'data:attachment/csv,' + encodeURIComponent(csvContent);
782    a.target = '_blank';
783    a.download = filename;
784
785    document.body.appendChild(a);
786    a.click();
787    document.body.removeChild(a);
788}
789function mapPlayers(dataArr) {
790    var count = dataArr.length, playerCount = 0;
791    var headerMappingObj = {
792        'NAME': 'Name', 'PLAYER': 'Name', 'POS': 'Pos', 'POSITION': 'Pos', 'POSITION(S)': 'Pos', 'POINTS': 'Points', 'PTS': 'Points', 'ADP': 'ADP', 'DOLLARS': 'Dollars', 'AAV': 'ADP', 'TEAM': 'Team'
793    };//Map titles in imported spreadsheet to Name, Points, ADP, Dollars, AAV, Pos, Team
794    var headerOrderObj = { 'Name': null, 'Pos': null, 'Team': null, 'Points': null, 'ADP': null, 'Dollars': null };
795    var mappedDataObj = {}, missingDataArr = [];
796    var importStats = [];
797    var header = null;
798    var flagLastFirst = false;
799
800    //Check headers
801    var headerArr = dataArr[0].split(',');
802    for (var i = 0; i < headerArr.length; i++) {//0 line must be header
803        header = headerArr[i].trim().toUpperCase();
804        if (headerMappingObj[header]) {
805            headerOrderObj[headerMappingObj[header]] = i;
806        }
807    }
808    if (headerOrderObj.Name === null) return;//No name field
809
810    if (headerOrderObj.Points != null) importStats.push('Points');
811    if (headerOrderObj.ADP != null) importStats.push('ADP');
812    if (headerOrderObj.Dollars != null) importStats.push('Dollars');
813
814    if (importStats.length === 0) return;//No stats to import
815
816    //Get PlayerID by Name
817    var playerID = null, arr = [], name = null;
818    for (var i = 1; i < count; i++) {//0 line is header
819        //arr = dataArr[i].replace(/['"]+/g, '').split(',');
820        arr = dataArr[i].split(',');
821
822        if (arr.length > 1) {
823            name = arr[headerOrderObj.Name].trim();
824            if (name != '') {
825                playerID = findPlayerName(name);
826                if (playerID === 0) {//Check for Last, First                    
827                    if (arr.length > headerArr.length) {
828                        name = arr[headerOrderObj.Name + 1].trim() + " " + arr[headerOrderObj.Name].trim();
829                        playerID = findPlayerName(name);
830                        if (playerID > 0 && !flagLastFirst) flagLastFirst = true;
831                    }
832                };
833                if (playerID > 0) {
834                    mappedDataObj[i] = playerID;
835                    playerCount++;
836                } else {
837                    missingDataArr.push(i);
838                }
839            }
840        }
841    }
842    //If Last, First then increment each header by one
843    if (flagLastFirst & headerOrderObj[importStats[0]] > headerOrderObj["Name"]) {
844        for (i = 0; i < importStats.length; i++) {
845            headerOrderObj[importStats[i]]++;
846        }
847    }
848
849    //Custom stats
850    var stat = null, arrFieldID = null, obj = {};
851    for (i = 0; i < importStats.length; i++) {//Loop through for each stat imported
852        stat = importStats[i];
853        arrFieldID = headerOrderObj[stat];
854        obj = {};
855        if (arrFieldID !== null) {
856            //Loop through stat field for all players
857            for (var j = 1; j < count; j++) {//0 line is header
858                if (mappedDataObj[j] > 0) obj[~~mappedDataObj[j]] = ~~dataArr[j].split(',')[arrFieldID];
859            }
860        }
861        switch (stat) {
862            case 'Points':
863                userDraftOptions.customPoints = clone(obj);
864                break;
865            case 'ADP':
866                userDraftOptions.customADP = clone(obj);
867                break;
868            case 'Dollars':
869                userDraftOptions.customDollars = clone(obj);
870                break;
871        }
872    }
873
874    //If missing players
875    var missingRowArr = null, missingData = [];
876    for (i = 0; i < missingDataArr.length; i++) {
877        missingRowArr = dataArr[missingDataArr[i]].split(',');
878        if (!flagLastFirst) {
879            missingData.push('<tr><td>' + missingRowArr[headerOrderObj.Name] + '</td><td>' + missingRowArr[headerOrderObj.Team] + '</td><td>' + missingRowArr[headerOrderObj.Pos] + '</td></tr>');
880        } else {
881            missingData.push('<tr><td>' + missingRowArr[headerOrderObj.Name + 1] + ' ' + missingRowArr[headerOrderObj.Name] + '</td><td>' + missingRowArr[headerOrderObj.Team + 1] + '</td><td>' + missingRowArr[headerOrderObj.Pos + 1] + '</td></tr>');
882        }
883
884        playerCount--;
885    }
886    var percent = 100;
887    if (missingData.length > 0) {
888        percent = ~~(100 * playerCount / (playerCount + missingData.length));
889        var html = `<span>Matched <span class="pull-right">${playerCount} (${percent}%). Missing these ${missingData.length} players:</span></span>
890                    <div class="progress"><div class="progress-bar bg-color-blue" style="width: ${percent}%;"></div></div>`;
891        if (missingData.length > 0) {
892            html += `<table>${missingData.join('')}</table>`;
893        }
894        document.getElementById('upload-container').innerHTML = html;
895    } else {
896        html = `<span>Players Matched <span class="pull-right">${playerCount} (${percent}%)</span></span>
897                    <div class="progress"><div class="progress-bar bg-color-blue" style="width: ${percent}%;"></div></div>`;
898        document.getElementById('upload-container').innerHTML = html;
899    }
900
901    if (headerOrderObj.ADP !== null) {
902        userDraftOptions.sourceADP = 'Custom';
903    }
904}
905function findPlayerName(name) {
906    if (playerMap[name]) {
907        return playerMap[name];
908    }
909
910    var str = name.replace(/[^\x20-\x7E]/g, '');
911    str = str.replace(" Jr.", "");
912    str = str.replace(" Sr.", "");
913    str = str.replace(" III", "");
914    str = str.replace(" II", "");
915    str = str.replace("\nQ", "");
916    str = str.replace("\nD", "");
917    str = str.replace("\nO", "");
918    str = str.replace("\nIR", "");
919    str = str.replace("\nSSPD", "");
920    //str = str.replace(/`/g, "'");
921    if (playerMap[str]) {
922        playerMap[name] = playerMap[str];
923        return playerMap[str];
924    }
925    str = name.replace(/[&\/\\#+$~%'"*?<>{}]/g, '');//remove odd characters like the back slash in DST
926    if (playerMap[str]) {
927        playerMap[name] = playerMap[str];
928        return playerMap[str];
929    }
930
931    str = name.replace(/[^\x20-\x7E]/g, "'");
932    if (playerMap[str]) {
933        playerMap[name] = playerMap[str];
934        return playerMap[str];
935    }
936
937    //change undefined team to FA
938    str = name.replace('undefined', 'FA');
939    if (playerMap[str]) {
940        playerMap[name] = playerMap[str];
941        return playerMap[str];
942    }
943    //send email if not found in above replace code
944    var obj = {
945        from: user.email,
946        subject: 'Filedrop did not find ' + name + ' on ' + settings.Host.Site,
947        msg: name
948    };
949    //sendEmailtoSupport(obj);
950
951    return 0;
952}
953
954function post(obj, url, callback) {
955    var xhr = new XMLHttpRequest();
956    xhr.open("POST", url, true);
957    xhr.setRequestHeader("Content-type", "application/json");
958    xhr.onreadystatechange = function () {
959        if (xhr.readyState === 4 && xhr.status === 200) {
960            var response = xhr.responseText || xhr.statusText;
961            callback(JSON.parse(response));
962        }
963    };
964    xhr.onloadend = function () {
965        if (xhr.status === 404) {
966            logError(url, window.location.href, 'post', 'xhr.onloadend', '404');
967        }
968    };
969    xhr.onerror = function (e) {
970        var str = JSON.stringify(obj);
971        logError(url, window.location.href, str, 'post: xhr.onerror', 'error');
972        console.log("Error on post " + url);
973    };
974    var data = JSON.stringify(obj);
975    xhr.send(data);
976}
977async function loadScript(url, callback) {
978    try {
979        const response = await fetch(url);
980        const scriptContent = await response.text();
981
982        const script = document.createElement('script');
983        script.textContent = scriptContent;
984        document.body.appendChild(script);
985        callback();
986    } catch (error) {
987        console.error('Error loading script: ', error);
988    }
989}
990function get(obj, url, callback) {
991    var xhr = new XMLHttpRequest();
992    xhr.open("GET", url, true);
993    xhr.setRequestHeader("Content-type", "application/json");
994    xhr.onreadystatechange = function () {
995        if (xhr.readyState === 4 && xhr.status === 200) {
996            var response = JSON.parse(xhr.responseText);
997            callback(response);
998        }
999    };
1000    xhr.onloadend = function () {
1001        if (xhr.status === 404) {
1002            logError(url, window.location.href, 'get', 'xhr.onloadend', '404');
1003        }
1004    };
1005    xhr.onerror = function (e) {
1006        logError(url, window.location.href, 'get', 'xhr.onerror', 'error');
1007        console.log("Error on get " + url);
1008    };
1009    var data = JSON.stringify(obj);
1010    xhr.send(data);
1011}
1012function getJSON(url, callback) {
1013    var xhr = new XMLHttpRequest();
1014    const timestamp = Date.now();
1015    url = url + '?version=' + timestamp;
1016    xhr.open("GET", url, true);
1017    xhr.setRequestHeader("Content-type", "application/json");
1018    xhr.setRequestHeader("Accept", "application/json");
1019    xhr.onreadystatechange = function () {
1020        if (xhr.readyState === 4 && xhr.status === 200) {
1021            var response = JSON.parse(xhr.responseText);
1022            callback(response);
1023        }
1024    };
1025    xhr.onloadend = function () {
1026        //if (xhr.status === 404) {
1027        //    logError(url, window.location.href, 'getJSON', 'xhr.onloadend', '404');
1028        //}
1029    //    if (xhr.status.toString().substring(0, 1) === "4") {
1030    //        logError(url, window.location.href, 'getJSON', 'xhr.onloadend', xhr.status.toString());
1031    //    }
1032    };
1033    xhr.onerror = function (e) {
1034        //logError(url, window.location.href, 'getJSON', 'xhr.onerror', e);
1035        //logError(url, window.location.href, 'getJSON', 'xhr.onerror', 'error');
1036        console.log("Error on getJSON " + url);
1037    };
1038    xhr.send();
1039}
1040function getJSONbySource(url, source, callback) {
1041    var xhr = new XMLHttpRequest();
1042    const timestamp = Date.now();
1043    url = url + '?version=' + timestamp;
1044    xhr.open("GET", url, true);
1045    xhr.setRequestHeader("Content-type", "application/json");
1046    xhr.setRequestHeader("Accept", "application/json");
1047    xhr.onreadystatechange = function () {
1048        if (xhr.readyState === 4 && xhr.status === 200) {
1049            var response = { 'json': JSON.parse(xhr.responseText), 'source': source };
1050            callback(response);
1051        }
1052    };
1053    xhr.onloadend = function () {
1054        if (xhr.status === 404) {
1055            var msg = source.source || source;
1056            logError(msg, window.location.href, 'getJSONbySource', 'xhr.onloadend', '404');
1057        }
1058    };
1059    xhr.onerror = function (e) {
1060        msg = source.source || source;
1061        logError(msg, window.location.href, 'getJSONbySource', 'xhr.onerror', 'error');
1062        console.log("Error on getJSONbySource " + msg);
1063    };
1064    try {
1065        xhr.send();
1066    } catch (e) {
1067        msg = source.source || source;
1068        logError(msg, window.location.href, 'getJSONbySource', 'catch', 'error');
1069        console.log("Error caught getJSONbySource " + url + " " + e);
1070    }
1071    
1072}
1073function getHTML(url, callback) {
1074    var xhr = new XMLHttpRequest();
1075    xhr.open("GET", url, true);
1076    xhr.onreadystatechange = function () {
1077        if (xhr.readyState === 4 && xhr.status === 200) {
1078            callback(xhr.responseText);
1079        }
1080    };
1081    xhr.onloadend = function () {
1082        if (xhr.status === 404) {
1083            logError(url, window.location.href, 'getHTML', 'xhr.onloadend', '404');
1084        }
1085    };
1086    xhr.onerror = function (e) {
1087        logError(url, window.location.href, 'getHTML', 'xhr.onerror', 'error');
1088        console.log("Error on getHTML " + url);
1089    };
1090    xhr.send();
1091}
1092
1093/*****************Notes**************************************************************
1094    Modal Controls
1095************************************************************************************/
1096
1097function openModal(info, header, action, callback) {
1098    //Update content
1099    if (document.getElementById('modal-info') && document.getElementById('modal-header')) {
1100        document.getElementById('modal-info').innerHTML = info;
1101        document.getElementById('modal-header').innerHTML = header;
1102        if (action) {
1103            document.getElementById('modal-button-action').innerHTML = action;
1104            //add actions
1105            document.getElementById('modal-button-action').style.display = 'inline-block';
1106            document.getElementById('modal-button-action').onclick = function () {
1107                callback();
1108                closeModal();
1109            };
1110        } else {
1111            document.getElementById('modal-button-action').style.display = 'none';
1112        }
1113        //Show modal
1114        document.getElementById('modal-button-close').innerHTML = 'Close';
1115        document.getElementById('modal-button-close').onclick = closeModal;
1116        document.getElementById('modal-window').style.display = "block";
1117    }
1118}
1119function openModalTarget(info, header, action, callback, target, targetCallback) {
1120    //Update content
1121    if (document.getElementById('modal-info-target') && document.getElementById('modal-header-target')) {
1122        document.getElementById('modal-info-target').innerHTML = info;
1123        document.getElementById('modal-header-target').innerHTML = header;
1124        
1125        //add actions
1126        document.getElementById('modal-button-target').innerHTML = action;
1127        document.getElementById('modal-button-target').style.display = 'inline-block';
1128        document.getElementById('modal-button-target').onclick = function () {
1129            callback();
1130            closeModalTarget();
1131        };
1132
1133        //add target next
1134        document.getElementById('modal-button-targetnext').innerHTML = target;
1135        document.getElementById('modal-button-targetnext').style.display = 'inline-block';
1136        document.getElementById('modal-button-targetnext').onclick = function () {
1137            targetCallback();
1138            closeModalTarget();
1139        };
1140
1141        //Show modal
1142        document.getElementById('modal-button-close-target').innerHTML = 'Close';
1143        document.getElementById('modal-button-close-target').onclick = closeModalTarget;
1144        document.getElementById('modal-window-target').style.display = "block";
1145
1146        
1147    }
1148}
1149function openModalCustom(info, header, action, callback, close, callbackclose, donotclose) {
1150    //Update content
1151    document.getElementById('modal-info').innerHTML = info;
1152    document.getElementById('modal-header').innerHTML = header;
1153    if (action) {
1154        document.getElementById('modal-button-action').innerHTML = action;
1155        //add action
1156        document.getElementById('modal-button-action').style.display = 'inline-block';
1157        document.getElementById('modal-button-action').onclick = function () {
1158            callback();
1159            if (!donotclose) {
1160                closeModal();
1161            }
1162        };
1163    } else {
1164        document.getElementById('modal-button-action').style.display = 'none';
1165    }
1166
1167    if (close) document.getElementById('modal-button-close').innerHTML = close;
1168    //add close action
1169    if (callbackclose) document.getElementById('modal-button-close').onclick = function () {
1170        callbackclose();
1171    };
1172
1173    //Show modal
1174    document.getElementById('modal-window').style.display = "block";
1175}
1176function openModalSelect(app) {
1177    var header = `<img src="../img/site/${leagueList[leagueSelected].site}" alt="${leagueList[leagueSelected].site} icon"> ${leagueList[leagueSelected].name}`;
1178    document.getElementById('modal-header-select').innerHTML = header;
1179
1180    switch (app) {
1181        case 'draft':
1182            //existing html is fine
1183            break;
1184        case 'auction':
1185            //Update content
1186            //<button type="button" class="button" style="display:inline-flex;align-items:center;height:29px" onclick="closeModalSelect();openApp('sync')"><img style="margin-right:8px" src="../../css/sprite.svg" class="svg svg-linkgray" height="15" width="15" alt="sync league icon" /> SYNC AUCTION</button>
1187            document.getElementById('modal-info-select').innerHTML = `<h2>IMPORTANT: For the most optimal dollar value calculations and recommendations, set your bench players to specific positions (QB,RB,WR,TE) instead of the generic BE. Contact us at [email protected] for any questions on the Auction a
1187pp.</h2><br><h2>Live Auction sync is not available. Enter the winning bids manually.</h2>`;
1188            
1189            document.getElementById('modal-buttons-select').innerHTML = `                
1190                <button type="button" class="button" style="display: inline-flex; align-items: center; height: 29px; margin-left: 6px" onclick="closeModalSelect();openApp('manual')"><img style="margin-right:8px" src="../../css/sprite.svg" class="svg svg-linkgray" height="15" width="15" alt="sync league icon" /> MANUAL AUCTION</button>
1191                <button type="button" class="button" style="height: 29px; float:right" onclick="openSettings()">CONFIRM SETTINGS</button>
1192            `;
1193            break;
1194    }
1195
1196    //Show modal
1197    document.getElementById('modal-window-select').style.display = "block";
1198}
1199function closeModal() {
1200    document.getElementById('modal-window').style.display = "none";
1201}
1202function closeModalTarget() {
1203    document.getElementById('modal-window-target').style.display = "none";
1204}
1205function closeModalSelect() {
1206    document.getElementById('modal-window-select').style.display = "none";
1207}
1208
1209//Local storage functions
1210
1211function saveLocal(item, obj) {
1212    if (typeof Storage !== "undefined") {// Code for localStorage/sessionStorage.
1213        var json = JSON.stringify(obj);
1214        localStorage.setItem(item, json);
1215    } else {
1216        alert('Please use Chrome or Edge browser to enable required local storage features.');
1217    }
1218}
1219function getLocal(item) {
1220    var json = {};
1221    var result = localStorage.getItem(item);
1222    if (result) {
1223        json = JSON.parse(result);
1224    } else {
1225        json = false;
1226    }
1227    return json;
1228}
1229function deleteLocal(item) {
1230    localStorage.removeItem(item);
1231}
1232function resetLocal() {
1233    for (item in localStorage) {
1234        localStorage.removeItem(item);
1235    }
1236}
1237const gcode = 'qaoufhasodasdfweflclkji';
1238
1239function clone(obj) {
1240    if (obj) {
1241        var newObj = {};
1242        for (var i = 0, keys = Object.keys(obj); i < keys.length; i++) {
1243            newObj[keys[i]] = obj[keys[i]];
1244        }
1245        return newObj;
1246    } else return {};
1247}
1248function hardRefresh() {
1249    location.reload(true);
1250}
1251var isChrome = window.chrome && !window.opr;
1252
1253function removeFromArr(item, arr) {
1254    var count = arr.length;
1255    for (var i = 0; i < count; i++) {
1256        if (arr[i] == item) {
1257            arr.splice(i, 1);
1258            return;
1259        }
1260    }
1261}
1262
1263var width = window.innerWidth
1264    || document.documentElement.clientWidth
1265    || document.body.clientWidth;
1266
1267var isMobile = width < 600 ? true : false;
1268//console.log('Browser window width is :', width);
1269
1270/*****************Notes**************************************************************
1271    Controllers for tabs
1272************************************************************************************/
1273
1274function addEventListenerTabs(callback) {
1275    var elements = document.getElementsByClassName("tab");
1276    for (var i = 0; i < elements.length; i++) {
1277        elements[i].addEventListener("click", function (event) {
1278            var element = event.target;
1279            if (element.getAttribute("data-tab") === null) {
1280                element = element.parentElement;
1281            }
1282            if (element.className.indexOf("active") === -1) {
1283                var id = element.getAttribute("data-tab");
1284                if (id !== null) {
1285                    toggleTabs(element);
1286                    element.classList.add("active");
1287                    if (callback) callback(id);
1288                }
1289            }
1290        });
1291    }
1292}
1293function toggleTabs(e) {
1294    //navigate to first tab sibling
1295    while (e.previousElementSibling && e.previousElementSibling.className.indexOf("tab") > -1) {
1296        e = e.previousElementSibling;
1297    }
1298
1299    //next siblings
1300    while (e && e.className.indexOf("tab") > -1) {
1301        //toggle sibling element
1302        if (e.className.indexOf("active") > -1) {
1303            e.classList.remove('active');
1304            var id = e.getAttribute("data-tab");
1305        }
1306        //iterate to next sibling
1307        e = e.nextElementSibling;
1308    }
1309}
1310
1311
1312/* Copyright 2015 William Summers, MetaTribal LLC
1313 * adapted from https://developer.mozilla.org/en-US/docs/JXON
1314 *
1315 * Licensed under the MIT License, Version 2.0 (the "License");
1316 * you may not use this file except in compliance with the License.
1317 * You may obtain a copy of the License at
1318 *
1319 *     https://opensource.org/licenses/MIT
1320 *
1321 * Unless required by applicable law or agreed to in writing, software
1322 * distributed under the License is distributed on an "AS IS" BASIS,
1323 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1324 * See the License for the specific language governing permissions and
1325 * limitations under the License.
1326 */
1327/**
1328 * @author William Summers
1329 *
1330 */
1331
1332var xmlToJSON = (function () {
1333
1334    this.version = "1.3.4";
1335
1336    var options = { // set up the default options
1337        mergeCDATA: true, // extract cdata and merge with text
1338        grokAttr: true, // convert truthy attributes to boolean, etc
1339        grokText: true, // convert truthy text/attr to boolean, etc
1340        normalize: true, // collapse multiple spaces to single space
1341        xmlns: true, // include namespaces as attribute in output
1342        //namespaceKey: '_ns', // tag name for namespace objects
1343        namespaceKey: 'realtime-fantasy-sports', // tag name for namespace objects
1344        textKey: '_text', // tag name for text nodes
1345        valueKey: '_value', // tag name for attribute values
1346        attrKey: '_attr', // tag for attr groups
1347        cdataKey: '_cdata', // tag for cdata nodes (ignored if mergeCDATA is true)
1348        attrsAsObject: true, // if false, key is used as prefix to name, set prefix to '' to merge children and attrs.
1349        stripAttrPrefix: true, // remove namespace prefixes from attributes
1350        stripElemPrefix: true, // for elements of same name in diff namespaces, you can enable namespaces and access the nskey property
1351        childrenAsArray: true // force children into arrays
1352    };
1353
1354    var prefixMatch = new RegExp(/(?!xmlns)^.*:/);
1355    var trimMatch = new RegExp(/^\s+|\s+$/g);
1356
1357    this.grokType = function (sValue) {
1358        if (/^\s*$/.test(sValue)) {
1359            return null;
1360        }
1361        if (/^(?:true|false)$/i.test(sValue)) {
1362            return sValue.toLowerCase() === "true";
1363        }
1364        if (isFinite(sValue)) {
1365            return parseFloat(sValue);
1366        }
1367        return sValue;
1368    };
1369
1370    this.parseString = function (xmlString, opt) {
1371        return this.parseXML(this.stringToXML(xmlString), opt);
1372    }
1373
1374    this.parseXML = function (oXMLParent, opt) {
1375
1376        // initialize options
1377        //for (var key in opt) {
1378        //    options[key] = opt[key];
1379        //}
1380
1381        var vResult = {},
1382            nLength = 0,
1383            sCollectedTxt = "";
1384
1385        // parse namespace information
1386        if (options.xmlns && oXMLParent.namespaceURI) {
1387            vResult[options.namespaceKey] = oXMLParent.namespaceURI;
1388        }
1389
1390        // parse attributes
1391        // using attributes property instead of hasAttributes method to support older browsers
1392        if (oXMLParent.attributes && oXMLParent.attributes.length > 0) {
1393            var vAttribs = {};
1394
1395            for (nLength; nLength < oXMLParent.attributes.length; nLength++) {
1396                var oAttrib = oXMLParent.attributes.item(nLength);
1397                vContent = {};
1398                var attribName = '';
1399
1400                if (options.stripAttrPrefix) {
1401                    attribName = oAttrib.name.replace(prefixMatch, '');
1402
1403                } else {
1404                    attribName = oAttrib.name;
1405                }
1406
1407                if (options.grokAttr) {
1408                    vContent[options.valueKey] = this.grokType(oAttrib.value.replace(trimMatch, ''));
1409                } else {
1410                    vContent[options.valueKey] = oAttrib.value.replace(trimMatch, '');
1411                }
1412
1413                if (options.xmlns && oAttrib.namespaceURI) {
1414                    vContent[options.namespaceKey] = oAttrib.namespaceURI;
1415                }
1416
1417                if (options.attrsAsObject) { // attributes with same local name must enable prefixes
1418                    vAttribs[attribName] = vContent;
1419                } else {
1420                    vResult[options.attrKey + attribName] = vContent;
1421                }
1422            }
1423
1424            if (options.attrsAsObject) {
1425                vResult[options.attrKey] = vAttribs;
1426            } else { }
1427        }
1428
1429        // iterate over the children
1430        if (oXMLParent.hasChildNodes()) {
1431            for (var oNode, sProp, vContent, nItem = 0; nItem < oXMLParent.childNodes.length; nItem++) {
1432                oNode = oXMLParent.childNodes.item(nItem);
1433
1434                if (oNode.nodeType === 4) {
1435                    if (options.mergeCDATA) {
1436                        sCollectedTxt += oNode.nodeValue;
1437                    } else {
1438                        if (vResult.hasOwnProperty(options.cdataKey)) {
1439                            if (vResult[options.cdataKey].constructor !== Array) {
1440                                vResult[options.cdataKey] = [vResult[options.cdataKey]];
1441                            }
1442                            vResult[options.cdataKey].push(oNode.nodeValue);
1443
1444                        } else {
1445                            if (options.childrenAsArray) {
1446                                vResult[options.cdataKey] = [];
1447                                vResult[options.cdataKey].push(oNode.nodeValue);
1448                            } else {
1449                                vResult[options.cdataKey] = oNode.nodeValue;
1450                            }
1451                        }
1452                    }
1453                } /* nodeType is "CDATASection" (4) */
1454                else if (oNode.nodeType === 3) {
1455                    sCollectedTxt += oNode.nodeValue;
1456                } /* nodeType is "Text" (3) */
1457                else if (oNode.nodeType === 1) { /* nodeType is "Element" (1) */
1458
1459                    if (nLength === 0) {
1460                        vResult = {};
1461                    }
1462
1463                    // using nodeName to support browser (IE) implementation with no 'localName' property
1464                    if (options.stripElemPrefix) {
1465                        sProp = oNode.nodeName.replace(prefixMatch, '');
1466                    } else {
1467                        sProp = oNode.nodeName;
1468                    }
1469
1470                    vContent = xmlToJSON.parseXML(oNode);
1471
1472                    if (vResult.hasOwnProperty(sProp)) {
1473                        if (vResult[sProp].constructor !== Array) {
1474                            vResult[sProp] = [vResult[sProp]];
1475                        }
1476                        vResult[sProp].push(vContent);
1477
1478                    } else {
1479                        if (options.childrenAsArray) {
1480                            vResult[sProp] = [];
1481                            vResult[sProp].push(vContent);
1482                        } else {
1483                            vResult[sProp] = vContent;
1484                        }
1485                        nLength++;
1486                    }
1487                }
1488            }
1489        } else if (!sCollectedTxt) { // no children and no text, return null
1490            if (options.childrenAsArray) {
1491                vResult[options.textKey] = [];
1492                vResult[options.textKey].push(null);
1493            } else {
1494                vResult[options.textKey] = null;
1495            }
1496        }
1497
1498        if (sCollectedTxt) {
1499            if (options.grokText) {
1500                var value = this.grokType(sCollectedTxt.replace(trimMatch, ''));
1501                if (value !== null && value !== undefined) {
1502                    vResult[options.textKey] = value;
1503                }
1504            } else if (options.normalize) {
1505                vResult[options.textKey] = sCollectedTxt.replace(trimMatch, '').replace(/\s+/g, " ");
1506            } else {
1507                vResult[options.textKey] = sCollectedTxt.replace(trimMatch, '');
1508            }
1509        }
1510
1511        return vResult;
1512    }
1513
1514
1515    // Convert xmlDocument to a string
1516    // Returns null on failure
1517    this.xmlToString = function (xmlDoc) {
1518        try {
1519            var xmlString = xmlDoc.xml ? xmlDoc.xml : (new XMLSerializer()).serializeToString(xmlDoc);
1520            return xmlString;
1521        } catch (err) {
1522            return null;
1523        }
1524    }
1525
1526    // Convert a string to XML Node Structure
1527    // Returns null on failure
1528    this.stringToXML = function (xmlString) {
1529        try {
1530            var xmlDoc = null;
1531
1532            if (window.DOMParser) {
1533
1534                var parser = new DOMParser();
1535                xmlDoc = parser.parseFromString(xmlString, "text/xml");
1536
1537                return xmlDoc;
1538            } else {
1539                xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
1540                xmlDoc.async = false;
1541                xmlDoc.loadXML(xmlString);
1542
1543                return xmlDoc;
1544            }
1545        } catch (e) {
1546            return null;
1547        }
1548    }
1549
1550    return this;
1551}).call({});
1552
1553if (typeof module != "undefined" && module !== null && module.exports) module.exports = xmlToJSON;
1554else if (typeof define === "function" && define.amd) define(function () { return xmlToJSON });
1555//adp
1556function loadADP() {
1557    return new Promise(function (resolve, reject) {
1558        var sites = ['CBS', 'ESPN', 'Yahoo', 'General', 'SFB'];
1559        var site = settings.Host.Site;//automatically includes host site adp
1560        if (!sites.includes(site)) sites.push(site);
1561        //include QB2 adp if 2QB league
1562        if (settings.isQB2) sites.push('QB2');        
1563        for (var i = 0; i < subscription.sites.length; i++) {
1564            site = subscription.sites[i];
1565            if (!sites.includes(site)) sites.push(site);
1566        }
1567        //dynasty
1568        if (settings.Draft['Advanced Options'] && settings.Draft['Advanced Options'].includes('Dynasty')) {
1569            sites.push("Dynasty");
1570        }
1571        //include options adp selection
1572        if (settings.Options && settings.Options.ADP) {
1573            if (settings.Options.ADP !== 'null' && !sites.includes(settings.Options.ADP)) sites.push(settings.Options.ADP);
1574        }
1575        var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/adp/`;
1576        var file = '';
1577        sitesLoaded = 0;
1578        for (i = 0; i < sites.length; i++) {
1579            file = sites[i] + '.json?version=' + dhversion;
1580            fetch(url + '' + file)
1581                .then(response => response.json())
1582                .then(data => {
1583                    var adp = data.adp;
1584                    var adpsite = data.site;
1585                    siteADP[adpsite] = adp;
1586                    //Update playersADPbySite
1587                    for (playerID in adp) {
1588                        if (playersADPBySite[playerID]) {
1589                            //playersADPBySite[playerID][adpsite] = emergencyObj.ADP[playerID] || adp[playerID];
1590                            playersADPBySite[playerID][adpsite] = adp[playerID];
1591                        }
1592                    }
1593
1594                    //resolve when all sites loaded
1595                    sitesLoaded++;
1596                    if (sitesLoaded === sites.length) {
1597                        resolve('success');
1598                    }
1599                })
1600                .catch((error) => {
1601                    console.log('loadADP Error:', error);
1602                });
1603        }
1604    });
1605}
1606function setupADP(adpSource) {
1607    var adpArrObj = [], adp = 0;
1608
1609    //Update playersADP
1610    if (adpSource === 'Custom' && userDraftOptions.customADP !== undefined) {
1611        var customADP = userDraftOptions.customADP;
1612        for (playerID in customADP) {
1613            adp = customADP[playerID];
1614            if (adp !== undefined) {                
1615                playersADP[playerID] = adp;
1616                adpArrObj.push({ 'id': ~~playerID, 'adp': adp });
1617                if (playersADPBySite[playerID]) playersADPBySite[playerID][adpSource] = adp;
1618            }
1619        }
1620    } else {
1621        //Check if 2QB league
1622        if (settings.isQB2 && adpSource !== "SFB" && adpSource.indexOf('Dynasty') === -1) {
1623            adpSource = 'QB2';
1624        }
1625        for (playerID in playersADPBySite) {
1626            adp = dndList.ADP[playerID] || playersADPBySite[playerID][adpSource];
1627            if (adp !== undefined) {
1628                playersADP[playerID] = adp;
1629                adpArrObj.push({ 'id': ~~playerID, 'adp': adp });
1630            } else {
1631                adp = playersADPBySite[playerID]['FFPC'] || playersADPBySite[playerID]['RTSports'] || playersADPBySite[playerID]['ESPN'] || playersADPBySite[playerID]['CBS'] || playersADPBySite[playerID]['Yahoo'] || playersADPBySite[playerID]['MFL'];
1632                if (adp) playersADP[playerID] = adp;
1633            }
1634        }
1635    }
1636    //Sort adpArrObj
1637    // Filter out players with adp <= 0 and sort by adp
1638    playersRankedByADP = adpArrObj
1639        .filter(player => player.adp > 0) // Filter out players with adp <= 0
1640        .sort((a, b) => a.adp - b.adp) // Sort by adp
1641        .map(player => player.id); // Extract player IDs
1642
1643
1644    //adpArrObj.sort(function (a, b) { return a.adp - b.adp; });
1645
1646    ////Store PlayerIDs by ADP
1647    //var count = Object.keys(adpArrObj).length;
1648    //var playersArr = new Array(count);
1649    //for (var i = 0; i < count; i++) {
1650    //    playersArr[i] = adpArrObj[i].id;
1651    //}
1652    //playersRankedByADP = playersArr;
1653
1654    //Update playersADP for Team Defense if missing
1655    for (i = 1; i < 33; i++) {
1656        playersADP[i] = playersADP[i] || 250;
1657    }
1658}
1659//player depth charts
1660function loadDepthCharts() {
1661    return new Promise(function (resolve, reject) {
1662        var url = 'https://drafthero.blob.core.windows.net/$web/' + currentSeason + '/playerLists/depthCharts.json';
1663        getJSON(url, function (result) {
1664            playersDepth = result;
1665            resolve('success');
1666        });
1667    });
1668}
1669
1670//player do not draft list
1671function loadDND() {
1672    return new Promise(function (resolve, reject) {
1673        var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerLists/DND.json`;
1674        getJSON(url, function (result) {
1675            //dndList = { "DND": {}, "ADP": {}, "Points": {} };
1676            dndList = result;
1677            resolve('success');
1678        });
1679    });
1680}
1681
1682//idp points and adp
1683async function loadIDPContent() {
1684    if (!settings.isIDP) return Promise.resolve('IDP not enabled');
1685
1686    try {
1687        const projectionsUrl = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/projections/IDP.Points.json?version=${dhversion}`;
1688        //const adpUrl = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/adp/IDP.json?version=${dhversion}`;
1689        const rankingsUrl = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/projections/idpRankings.json?version=${dhversion}`;
1690
1691        const [pointsResponse, rankingsResponse] = await Promise.all([fetch(projectionsUrl), fetch(rankingsUrl)]);//adpResponse, fetch(adpUrl), 
1692        const pointsData = await pointsResponse.json();
1693        //const adpData = await adpResponse.json();
1694        const rankingsData = await rankingsResponse.json();
1695
1696        const points = pointsData.fpts;
1697        //const adp = adpData.adp;
1698        const rankings = rankingsData.idpRankings;
1699
1700        for (const playerID in points) {
1701            if (playersObj[playerID]) {
1702                playersPoints[playerID] = points[playerID];
1703            } else {
1704                console.log(`Missing player in playersObj: ${playerID}`);
1705            }
1706        }
1707
1708        //const sortableArr = Object.keys(adp)
1709        //    .filter(playerID => playersObj[playerID])
1710        //    .map(playerID => ({ id: playerID, ADP: adp[playerID] }))
1711        //    .sort((a, b) => a.ADP - b.ADP);
1712
1713        //idpRankedByADP = sortableArr.map(player => player.id);
1714
1715        //rankings
1716        idpRankedByADP = [];
1717        for (let i = 0; i < rankings.length; i++) {
1718            // Add the first element of each sub-array to the new array
1719            idpRankedByADP.push(rankings[i][0]);
1720        }
1721        //rank by points
1722        //idpRankedByADP = Object.entries(points)
1723        //    .sort(([, a], [, b]) => b - a)
1724        //    .map(([key, _]) => key);
1725
1726        return 'success';
1727    } catch (error) {
1728        console.log('Error loading IDP content:', error);
1729        return Promise.reject('Error loading IDP content');
1730    }
1731}
1732
1733function loadIDPContentOLD() {
1734    return new Promise(function (resolve, reject) {        
1735        if (settings.isIDP) {
1736            url = 'https://drafthero.blob.core.windows.net/$web/' + currentSeason + '/projections/IDP.Points.json?version=' + dhversion;
1737            fetch(url)
1738                .then(response => response.json())
1739                .then(data => {
1740                    var players = data.fpts;
1741                    for (playerID in players) {
1742                        if (playersObj[playerID]) {
1743                            playersPoints[playerID] = players[playerID];
1744                        } else {
1745                            console.log(playerID);
1746                        }
1747                    }
1748                    url = 'https://drafthero.blob.core.windows.net/$web/' + currentSeason + '/adp/IDP.json?version=' + dhversion;
1749                    fetch(url)
1750                        .then(response => response.json())
1751                        .then(data => {
1752                            var players = data.adp;
1753                            var sortableArr = [];
1754                            for (playerID in players) {
1755                                if (playersObj[playerID]) {
1756                                    sortableArr.push({ 'id': playerID, 'ADP': players[playerID] });
1757                                    
1758                                    playersADP[playerID] = players[playerID];
1759                                } else {
1760                                    console.log(playerID);
1761                                }
1762                            }
1763                            sortableArr.sort(function (a, b) { return a.ADP - b.ADP });
1764                            //idpRankedByADP[playerID] = players[playerID];
1765                            idpRankedByADP = [];
1766                            sortableArr.forEach(function (player, index) {
1767                                idpRankedByADP.push(player.id);
1768                            });
1769
1770                            resolve('success');
1771                        })
1772                        .catch((error) => {
1773                            console.log('load IDP Points Error:', error);
1774                        });
1775                })
1776                .catch((error) => {
1777                    console.log('load IDP Points Error:', error);
1778                });
1779        }
1780    });
1781}
1782//player news
1783function loadNews() {
1784    return new Promise(function (resolve, reject) {
1785        var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerNews/breaking.json`;
1786        getJSON(url, function (result) {
1787            playerNews = result;
1788            resolve('success');
1789        });
1790    });
1791}
1792
1793//player outlooks
1794function loadOutlooksOLD() {
1795    return new Promise(function (resolve, reject) {
1796        var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerNotes/4for4/playerNotes.json`;
1797        getJSON(url, function (result) {
1798            playerNotes['4for4'] = result.playerNotes;
1799        });
1800    });
1801}
1802function loadOutlooks() {
1803    return new Promise(function (resolve, reject) {
1804        var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerNotes/4for4/playerNotes.json`;
1805
1806        getJSON(url, function (error, result) {
1807            if (error) {
1808                // If there is an error, reject the promise with an error message
1809                if (error.status === 404) {
1810                    reject(new Error('Resource not found: ' + url));
1811                } else {
1812                    reject(new Error('An error occurred while fetching the data: ' + error.message));
1813                }
1814            } else {
1815                // If the request is successful, resolve the promise
1816                playerNotes['4for4'] = result.playerNotes;
1817                resolve();
1818            }
1819        });
1820    });
1821}
1822
1823//player photos
1824function getPhoto(playerID, size) {
1825    const str = playersObj[playerID] ? playersObj[playerID].NameObj.str : '0';
1826    const name = playersObj[playerID] ? playersNames[playerID] : 'notfound';
1827    const url = `<img src="https://drafthero.blob.core.windows.net/$web/img/photos/64/${str}.webp" 
1828    srcset="https://drafthero.blob.core.windows.net/$web/img/photos/64/${str}.webp 1x, https://drafthero.blob.core.windows.net/$web/img/photos/128/${str}.webp 2x" 
1829    alt="Photo of ${name} for fantasy football" width="${size}" height="${size}" style="border-radius: 10%" onerror="this.onerror=null;this.srcset='https://drafthero.blob.core.windows.net/$web/img/photos/64/0.webp'">`;
1830
1831    return url;
1832}
1833//function getPhoto(playerID, size) {//https://www44.myfantasyleague.com/player_photos_2014/13130_thumb.jpg
1834//    //const str = playersObj[playerID] ? playersObj[playerID].NameObj.str : '0';
1835//    const name = playersObj[playerID] ? playersNames[playerID] : 'notfound';
1836//    var mflPlayerID = 0;
1837//    var photoURL = "https://drafthero.blob.core.windows.net/$web/img/photos/64/0.webp";
1838//    if (mflPlayerIDs[playerID]) {
1839//        mflPlayerID = mflPlayerIDs[playerID];
1840//        photoURL = `https://www44.myfantasyleague.com/player_photos_2014/${mflPlayerID}_thumb.jpg`;
1841//    }
1842    
1843//    const url = `<img src="${photoURL}" alt="Photo of ${name} for fantasy football" width="128" height="128" style="border-radius: 10%">`;
1844
1845//    return url;
1846//}
1847
1848var mflPlayerIDs = {
1849    1350: 5848,
1850    3766: 7394,
1851    4307: 7836,
1852    4891: 8269,
1853    5051: 8930,
1854    5365: 8851,
1855    5648: 9064,
1856    5653: 9207,
1857    5971: 9431,
1858    6045: 9694,
1859    7355: 10313,
1860    7357: 10413,
1861    8043: 10703,
1862    8057: 10700,
1863    8065: 10948,
1864    8398: 10976,
1865    8619: 11232,
1866    8627: 11222,
1867    8743: 11150,
1868    8781: 11247,
1869    8783: 11244,
1870    8792: 11225,
1871    8896: 11387,
1872    8930: 11367,
1873    8986: 11938,
1874    9245: 11640,
1875    9253: 11671,
1876    9255: 11679,
1877    9260: 11674,
1878    9317: 11644,
1879    9320: 11760,
1880    9455: 11675,
1881    9633: 11945,
1882    9646: 11936,
1883    9948: 11947,
1884    10033: 12955,
1885    10037: 12140,
1886    10055: 12175,
1887    10074: 12141,
1888    10126: 12155,
1889    10132: 12181,
1890    10133: 12186,
1891    10157: 12437,
1892    10161: 12187,
1893    10179: 12476,
1894    10208: 12188,
1895    10215: 12263,
1896    10234: 12394,
1897    10468: 12325,
1898    10483: 12381,
1899    10604: 12447,
1900    10606: 12608,
1901    10729: 12611,
1902    10735: 12676,
1903    10736: 12625,
1904    10739: 12645,
1905    10748: 12677,
1906    10819: 12626,
1907    10822: 12650,
1908    10854: 12678,
1909    10856: 12610,
1910    10882: 12621,
1911    10919: 12616,
1912    10949: 12680,
1913    10988: 12658,
1914    10994: 12773,
1915    11008: 12620,
1916    11026: 12623,
1917    11031: 12860,
1918    11163: 12785,
1919    11191: 12930,
1920    11222: 12801,
1921    11309: 12956,
1922    11429: 13066,
1923    11599: 12923,
1924    11685: 13134,
1925    11690: 13130,
1926    11691: 13146,
1927    11698: 13133,
1928    11700: 13128,
1929    11707: 13131,
1930    11709: 13115,
1931    11710: 13157,
1932    11712: 13113,
1933    11718: 13163,
1934    11732: 13132,
1935    11734: 13192,
1936    11737: 13236,
1937    11739: 13138,
1938    11751: 13176,
1939    11763: 13319,
1940    11767: 13143,
1941    11768: 13148,
1942    11777: 13139,
1943    11783: 13354,
1944    11805: 13189,
1945    11807: 13193,
1946    11831: 13347,
1947    11834: 13119,
1948    11839: 13116,
1949    11841: 13124,
1950    11847: 13418,
1951    11849: 13172,
1952    11861: 13289,
1953    11863: 13164,
1954    11868: 13488,
1955    11871: 13168,
1956    11877: 13156,
1957    11881: 13316,
1958    11885: 13154,
1959    11887: 13994,
1960    11892: 13299,
1961    11897: 13315,
1962    12061: 13377,
1963    12063: 13424,
1964    12196: 13246,
1965    12203: 13303,
1966    12205: 13308,
1967    12216: 13348,
1968    12235: 13919,
1969    12292: 13403,
1970    12298: 13455,
1971    12316: 13940,
1972    12389: 13427,
1973    12401: 13404,
1974    12447: 13862,
1975    12467: 13680,
1976    12477: 13635,
1977    12483: 13589,
1978    12490: 13592,
1979    12507: 13604,
1980    12508: 13633,
1981    12511: 13968,
1982    12514: 13772,
1983    12559: 13671,
1984    12561: 13593,
1985    12568: 13753,
1986    12570: 13641,
1987    12582: 13719,
1988    12586: 13630,
1989    12616: 13629,
1990    12619: 13590,
1991    12623: 13879,
1992    12628: 13632,
1993    12629: 13595,
1994    12630: 13622,
1995    12667: 13726,
1996    12678: 14331,
1997    12746: 13776,
1998    12764: 13672,
1999    12771: 13638,
2000    12773: 13652,
2001    12775: 13980,
2002    12807: 13676,
2003    12809: 13675,
2004    12820: 13668,
2005    12826: 13598,
2006    12842: 13718,
2007    12858: 13678,
2008    12860: 13674,
2009    12884: 13634,
2010    12886: 13610,
2011    12932: 14017,
2012    12934: 13793,
2013    12986: 13763,
2014    12992: 13809,
2015    13007: 13832,
2016    13030: 13988,
2017    13061: 13893,
2018    13123: 13850,
2019    13266: 13898,
2020    13286: 14370,
2021    13378: 13669,
2022    13403: 15035,
2023    13418: 13963,
2024    13424: 14102,
2025    13426: 14137,
2026    13427: 14086,
2027    13429: 14136,
2028    13432: 14104,
2029    13438: 14063,
2030    13446: 14062,
2031    13449: 14080,
2032    13466: 14143,
2033    13469: 14108,
2034    13472: 14208,
2035    13477: 14223,
2036    13484: 14083,
2037    13491: 14059,
2038    13502: 14105,
2039    13504: 14358,
2040    13505: 14081,
2041    13517: 14127,
2042    13521: 14079,
2043    13522: 14122,
2044    13525: 14112,
2045    13532: 14113,
2046    13536: 14109,
2047    13548: 14076,
2048    13551: 14060,
2049    13556: 14071,
2050    13565: 14118,
2051    13582: 14073,
2052    13583: 14141,
2053    13587: 14095,
2054    13590: 14085,
2055    13602: 14341,
2056    13610: 14138,
2057    13613: 14056,
2058    13620: 14065,
2059    13627: 14069,
2060    13634: 14110,
2061    13673: 14244,
2062    13713: 14319,
2063    13736: 14057,
2064    13741: 14067,
2065    13749: 14123,
2066    13786: 14209,
2067    13806: 14420,
2068    13809: 14146,
2069    13822: 14239,
2070    13833: 14592,
2071    13854: 14315,
2072    13894: 15981,
2073    13911: 14548,
2074    13931: 14359,
2075    13960: 14727,
2076    13987: 14280,
2077    14055: 14613,
2078    14129: 14486,
2079    14172: 14600,
2080    14189: 14426,
2081    14246: 14503,
2082    14254: 14717,
2083    14318: 14555,
2084    14356: 14849,
2085    14358: 14838,
2086    14359: 14845,
2087    14369: 14873,
2088    14370: 14805,
2089    14371: 14782,
2090    14376: 14860,
2091    14378: 14842,
2092    14383: 14799,
2093    14386: 14840,
2094    14394: 14797,
2095    14410: 14815,
2096    14411: 14832,
2097    14415: 14848,
2098    14416: 14783,
2099    14418: 14800,
2100    14421: 14839,
2101    14430: 14858,
2102    14432: 14864,
2103    14433: 14841,
2104    14436: 14867,
2105    14442: 14777,
2106    14446: 14779,
2107    14453: 14811,
2108    14455: 14802,
2109    14457: 14857,
2110    14458: 14833,
2111    14463: 14871,
2112    14464: 14992,
2113    14465: 14778,
2114    14473: 14834,
2115    14478: 15097,
2116    14495: 15589,
2117    14506: 14835,
2118    14509: 14836,
2119    14510: 14974,
2120    14514: 14803,
2121    14541: 14870,
2122    14563: 14789,
2123    14564: 14798,
2124    14573: 14817,
2125    14576: 14875,
2126    14597: 15041,
2127    14608: 14984,
2128    14609: 14983,
2129    14629: 14876,
2130    14630: 14861,
2131    14636: 14847,
2132    14639: 14852,
2133    14641: 14977,
2134    14657: 14823,
2135    14750: 15109,
2136    14760: 17439,
2137    14844: 15103,
2138    14858: 15535,
2139    14942: 16037,
2140    15183: 15281,
2141    15198: 15240,
2142    15200: 15290,
2143    15210: 15293,
2144    15214: 15308,
2145    15222: 15255,
2146    15229: 15301,
2147    15242: 15261,
2148    15245: 15419,
2149    15246: 15334,
2150    15262: 15248,
2151    15265: 15329,
2152    15267: 15331,
2153    15273: 15258,
2154    15274: 15292,
2155    15280: 15288,
2156    15281: 15271,
2157    15287: 15434,
2158    15289: 15344,
2159    15294: 15454,
2160    15296: 15262,
2161    15306: 15256,
2162    15310: 15285,
2163    15315: 15260,
2164    15324: 15244,
2165    15328: 15250,
2166    15330: 15239,
2167    15332: 15287,
2168    15335: 15242,
2169    15336: 15289,
2170    15343: 15462,
2171    15357: 15237,
2172    15369: 15330,
2173    15371: 15332,
2174    15372: 15259,
2175    15376: 16499,
2176    15379: 15263,
2177    15383: 15368,
2178    15385: 15253,
2179    15402: 15241,
2180    15404: 15284,
2181    15406: 15282,
2182    15413: 15257,
2183    15416: 15238,
2184    15447: 15319,
2185    15459: 15337,
2186    15460: 15341,
2187    15461: 15302,
2188    15464: 16005,
2189    15467: 15491,
2190    15468: 15254,
2191    15510: 15541,
2192    15512: 15369,
2193    15533: 15995,
2194    15542: 15252,
2195    15559: 15303,
2196    15566: 15335,
2197    15579: 15414,
2198    15663: 16120,
2199    15681: 15626,
2200    15701: 15604,
2201    15744: 15650,
2202    15786: 15671,
2203    15802: 15721,
2204    15803: 15758,
2205    15804: 15782,
2206    15807: 15710,
2207    15810: 15708,
2208    15821: 15840,
2209    15825: 15693,
2210    15828: 15738,
2211    15829: 15753,
2212    15831: 15787,
2213    15834: 15698,
2214    15838: 15757,
2215    15841: 15692,
2216    15846: 15767,
2217    15847: 15734,
2218    15849: 15756,
2219    15850: 15755,
2220    15852: 15714,
2221    15853: 15762,
2222    15868: 15751,
2223    15869: 16135,
2224    15872: 15694,
2225    15874: 15777,
2226    15875: 15717,
2227    15876: 15715,
2228    15891: 15797,
2229    15892: 15761,
2230    15893: 15718,
2231    15895: 15754,
2232    15896: 15752,
2233    15902: 15794,
2234    15907: 15716,
2235    15909: 15711,
2236    15910: 15763,
2237    15916: 15800,
2238    15917: 15841,
2239    15918: 15771,
2240    15921: 15965,
2241    15922: 15941,
2242    15923: 15803,
2243    15924: 15795,
2244    15928: 15697,
2245    15935: 15785,
2246    15946: 15789,
2247    15947: 15799,
2248    15974: 15699,
2249    15975: 15696,
2250    15976: 15712,
2251    15979: 15723,
2252    15984: 15742,
2253    15989: 15713,
2254    15990: 15730,
2255    15992: 15720,
2256    15996: 15796,
2257    16005: 15766,
2258    16010: 15788,
2259    16022: 15768,
2260    16025: 15786,
2261    16026: 15765,
2262    16027: 15779,
2263    16032: 15802,
2264    16033: 15798,
2265    16034: 15905,
2266    16035: 15909,
2267    16036: 15873,
2268    16039: 15804,
2269    16042: 15889,
2270    16069: 15759,
2271    16102: 15733,
2272    16104: 15749,
2273    16105: 15727,
2274    16107: 15743,
2275    16108: 15726,
2276    16163: 15972,
2277    16170: 15925,
2278    16226: 15979,
2279    16227: 15882,
2280    16259: 15970,
2281    16277: 15916,
2282    16282: 15929,
2283    16291: 15948,
2284    16294: 15953,
2285    16321: 16113,
2286    16333: 15977,
2287    16337: 15772,
2288    16338: 15991,
2289    16355: 15973,
2290    16418: 16036,
2291    16472: 16877,
2292    16530: 17375,
2293    16559: 16405,
2294    16560: 16080,
2295    16627: 16557,
2296    16641: 16066,
2297    16646: 16043,
2298    16649: 16222,
2299    16653: 16152,
2300    16654: 16185,
2301    16656: 16210,
2302    16658: 16195,
2303    16661: 16167,
2304    16663: 16215,
2305    16665: 16149,
2306    16666: 16212,
2307    16676: 16180,
2308    16686: 15702,
2309    16689: 16187,
2310    16690: 16192,
2311    16691: 16157,
2312    16694: 16287,
2313    16697: 16213,
2314    16701: 16359,
2315    16705: 16164,
2316    16706: 16191,
2317    16713: 16205,
2318    16714: 16183,
2319    16716: 16159,
2320    16719: 16203,
2321    16720: 16218,
2322    16728: 16166,
2323    16730: 16395,
2324    16735: 16168,
2325    16739: 16161,
2326    16741: 16387,
2327    16743: 16379,
2328    16744: 16422,
2329    16746: 16447,
2330    16751: 16342,
2331    16767: 16335,
2332    16772: 16175,
2333    16790: 16211,
2334    16795: 16193,
2335    16797: 16441,
2336    16802: 16196,
2337    16807: 16148,
2338    16808: 16162,
2339    16810: 16178,
2340    16834: 16403,
2341    16843: 16202,
2342    16847: 17011,
2343    16849: 16189,
2344    16855: 16221,
2345    16866: 16312,
2346    16871: 16153,
2347    16886: 16150,
2348    16889: 16216,
2349    16895: 16186,
2350    16897: 16169,
2351    16898: 16171,
2352    16899: 16188,
2353    16905: 16151,
2354    16912: 16156,
2355    16913: 16160,
2356    16919: 16190,
2357    16931: 16217,
2358    16932: 16201,
2359    16933: 16174,
2360    16934: 16181,
2361    16935: 16204,
2362    16936: 16199,
2363    16937: 16197,
2364    16939: 16299,
2365    16941: 16165,
2366    16946: 16384,
2367    16957: 16206,
2368    16959: 16333,
2369    16963: 16194,
2370    16965: 16207,
2371    16970: 16173,
2372    16977: 16184,
2373    16992: 16474,
2374    16995: 16582,
2375    16997: 17030,
2376    17000: 16391,
2377    17002: 16170,
2378    17003: 16176,
2379    17008: 16640,
2380    17010: 16367,
2381    17011: 16625,
2382    17012: 16483,
2383    17014: 16220,
2384    17015: 16686,
2385    17017: 16269,
2386    17020: 16616,
2387    17021: 16649,
2388    17026: 16746,
2389    17030: 16219,
2390    17032: 17732,
2391    17033: 16214,
2392    17034: 16249,
2393    17037: 17039,
2394    17039: 16605,
2395    17040: 16393,
2396    17043: 16629,
2397    17045: 16416,
2398    17048: 16748,
2399    17051: 16801,
2400    17057: 16752,
2401    17061: 16313,
2402    17062: 16453,
2403    17081: 17254,
2404    17091: 16385,
2405    17107: 16292,
2406    17125: 16424,
2407    17126: 16380,
2408    17130: 16940,
2409    17132: 16419,
2410    17133: 16286,
2411    17165: 16339,
2412    17182: 16423,
2413    17184: 16413,
2414    17185: 16364,
2415    17218: 16919,
2416    17235: 16496,
2417    17266: 16868,
2418    17271: 16958,
2419    17319: 16390,
2420    17324: 16523,
2421    17330: 16949,
2422    17334: 16533,
2423    17335: 16425,
2424    17376: 16846,
2425    17378: 16410,
2426    17413: 16354,
2427    17465: 16864,
2428    17467: 16428,
2429    17510: 17337,
2430    17555: 16451,
2431    17584: 16850,
2432    17593: 16432,
2433    17595: 16444,
2434    17626: 16429,
2435    17649: 16414,
2436    17664: 16587,
2437    17668: 16626,
2438    17670: 16811,
2439    17673: 16580,
2440    17674: 16614,
2441    17684: 16631,
2442    17686: 16624,
2443    17687: 16620,
2444    17688: 16633,
2445    17689: 16584,
2446    17690: 16593,
2447    17692: 16581,
2448    17693: 16641,
2449    17695: 16579,
2450    17696: 16645,
2451    17697: 16806,
2452    17700: 16583,
2453    17702: 16596,
2454    17704: 16615,
2455    17716: 16618,
2456    17719: 16619,
2457    17724: 16627,
2458    17728: 16621,
2459    17732: 16617,
2460    17734: 16639,
2461    17738: 16632,
2462    17747: 16597,
2463    17748: 16643,
2464    17756: 16623,
2465    17760: 16678,
2466    17761: 16622,
2467    17763: 16628,
2468    17767: 16594,
2469    17772: 17098,
2470    17777: 16636,
2471    17782: 16586,
2472    17784: 16598,
2473    17787: 16637,
2474    17788: 16595,
2475    17792: 16603,
2476    17794: 16642,
2477    17795: 16604,
2478    17800: 16610,
2479    17802: 16601,
2480    17811: 16723,
2481    17818: 16613,
2482    17820: 16635,
2483    17821: 16757,
2484    17829: 16600,
2485    17830: 16585,
2486    17833: 16644,
2487    17848: 16646,
2488    17853: 16599,
2489    17858: 17255,
2490    17863: 16684,
2491    17866: 16765,
2492    17894: 16648,
2493    17908: 16647,
2494    17909: 16612,
2495    17915: 16803,
2496    17916: 16773,
2497    17918: 16882,
2498    17919: 17359,
2499    17921: 16843,
2500    17922: 16681,
2501    17923: 16775,
2502    17926: 16680,
2503    17942: 16751,
2504    17947: 16688,
2505    17965: 16796,
2506    17966: 16706,
2507    17967: 16747,
2508    17968: 16608,
2509    17970: 16606,
2510    17971: 16732,
2511    17972: 16758,
2512    17974: 16778,
2513    17975: 16788,
2514    17976: 16809,
2515    17977: 16630,
2516    17978: 16792,
2517    17979: 16682,
2518    18034: 17424,
2519    18036: 16687,
2520    18041: 16634,
2521    18044: 16760,
2522    18073: 16763,
2523    18083: 16839,
2524    18107: 16858,
2525    18118: 16810,
2526    18127: 16821,
2527    18160: 16898,
2528    18195: 16854,
2529    18251: 17407,
2530    18253: 16962,
2531    18260: 16871,
2532    18268: 16896,
2533    18281: 16834,
2534    18284: 16979,
2535    18294: 16848,
2536    18295: 16837,
2537    18300: 16816,
2538    18301: 16831,
2539    18302: 16975,
2540    18307: 16883,
2541    18328: 16838,
2542    18368: 16957,
2543    18443: 17060,
2544    18444: 17057,
2545    18445: 17044,
2546    18448: 17308,
2547    18449: 17053,
2548    18450: 17059,
2549    18451: 17709,
2550    18453: 17276,
2551    18455: 17100,
2552    18457: 17078,
2553    18458: 17275,
2554    18460: 17104,
2555    18463: 17050,
2556    18466: 17077,
2557    18467: 17047,
2558    18472: 17103,
2559    18474: 17281,
2560    18476: 17061,
2561    18477: 17033,
2562    18478: 17072,
2563    18479: 17031,
2564    18480: 17334,
2565    18481: 17099,
2566    18482: 17040,
2567    18484: 17079,
2568    18485: 17055,
2569    18487: 17042,
2570    18496: 17037,
2571    18497: 17107,
2572    18498: 17038,
2573    18499: 17069,
2574    18500: 17075,
2575    18502: 17076,
2576    18503: 17105,
2577    18505: 17034,
2578    18506: 17056,
2579    18507: 17243,
2580    18508: 17277,
2581    18509: 17736,
2582    18511: 17726,
2583    18512: 17036,
2584    18513: 17035,
2585    18514: 17045,
2586    18515: 17048,
2587    18516: 17051,
2588    18517: 17043,
2589    18518: 17476,
2590    18519: 17058,
2591    18520: 17066,
2592    18521: 17046,
2593    18522: 17068,
2594    18523: 17064,
2595    18524: 17052,
2596    18526: 17062,
2597    18527: 17329,
2598    18528: 17054,
2599    18529: 17049,
2600    18530: 17479,
2601    18531: 17063,
2602    18532: 17274,
2603    18533: 17478,
2604    18534: 17102,
2605    18535: 17106,
2606    18536: 17285,
2607    18538: 17213,
2608    18539: 17544,
2609    18540: 17108,
2610    18542: 17268,
2611    18544: 17567,
2612    18545: 17109,
2613    18546: 17111,
2614    18547: 17071,
2615    18548: 17070,
2616    18549: 17081,
2617    18550: 17086,
2618    18551: 17083,
2619    18552: 17073,
2620    18553: 17097,
2621    18554: 17096,
2622    18555: 17080,
2623    18558: 17087,
2624    18559: 17089,
2625    18560: 17084,
2626    18561: 17091,
2627    18562: 17082,
2628    18565: 17041,
2629    18566: 17266,
2630    18567: 17232,
2631    18569: 17234,
2632    18573: 17231,
2633    18574: 17032,
2634    18578: 17074,
2635    18621: 17312,
2636    18622: 17284,
2637    18623: 17259,
2638    18624: 17215,
2639    18640: 17307,
2640    18641: 17171,
2641    18642: 17157,
2642    18643: 17184,
2643    18645: 17088,
2644    18646: 17175,
2645    18647: 17192,
2646    18648: 17303,
2647    18650: 17205,
2648    18652: 17282,
2649    18653: 17227,
2650    18654: 17283,
2651    18742: 17222,
2652    18743: 17220,
2653    18744: 17434,
2654    18774: 17133,
2655    18808: 17240,
2656    18811: 17256,
2657    18816: 17238,
2658    18822: 17253,
2659    18824: 17260,
2660    18838: 17298,
2661    18841: 17396,
2662    18857: 17293,
2663    18860: 17289,
2664    18862: 17309,
2665    18863: 17385,
2666    18864: 17382,
2667    18869: 17313,
2668    18877: 17278,
2669    18886: 17267,
2670    18889: 17269,
2671    18894: 17348,
2672    18906: 17419,
2673    18919: 17321,
2674    18938: 17353,
2675    18940: 17322,
2676    18941: 17349,
2677    18943: 17336,
2678    18944: 17305,
2679    18947: 17444,
2680    18949: 17272,
2681    18958: 17333,
2682    18968: 17414,
2683    19094: 17416,
2684    19095: 17360,
2685    19111: 17314,
2686    19125: 17325,
2687    19126: 17134,
2688    19230: 17517,
2689    19231: 17499,
2690    19233: 17498,
2691    19234: 17481,
2692    19235: 17472,
2693    19237: 17512,
2694    19239: 17509,
2695    19240: 17474,
2696    19241: 17477,
2697    19242: 17501,
2698    19245: 17487,
2699    19251: 17508,
2700    19258: 17545,
2701    19259: 17521,
2702    19262: 17507,
2703    19263: 17503,
2704    19268: 17497,
2705    19275: 17463,
2706    19276: 17548,
2707    19278: 17547,
2708    19279: 17543,
2709    19281: 17462,
2710    19283: 17465,
2711    19284: 17467,
2712    19286: 17468,
2713    19287: 17466,
2714    19289: 17657,
2715    19290: 17471,
2716    19292: 17469,
2717    19293: 17702,
2718    19295: 17470,
2719    19296: 17654,
2720    19298: 17500,
2721    19299: 17473,
2722    19300: 17482,
2723    19301: 17652,
2724    19302: 17486,
2725    19303: 17475,
2726    19304: 17483,
2727    19305: 17490,
2728    19306: 17491,
2729    19308: 17484,
2730    19309: 17506,
2731    19310: 17505,
2732    19312: 17566,
2733    19313: 17511,
2734    19314: 17502,
2735    19315: 17510,
2736    19316: 17504,
2737    19318: 17529,
2738    19319: 17519,
2739    19320: 17520,
2740    19321: 17527,
2741    19322: 17528,
2742    19323: 17515,
2743    19324: 17516,
2744    19325: 17513,
2745    19326: 17530,
2746    19327: 17707,
2747    19329: 17524,
2748    19330: 17514,
2749    19331: 17538,
2750    19332: 17525,
2751    19333: 17537,
2752    19334: 17535,
2753    19335: 17685,
2754    19336: 17541,
2755    19337: 17536,
2756    19339: 17522,
2757    19340: 17620,
2758    19343: 17599,
2759    19344: 17523,
2760    19345: 17742,
2761    19346: 17546,
2762    19348: 17607,
2763    19349: 17644,
2764    19351: 17710,
2765    19352: 17593,
2766    19354: 17724,
2767    19357: 17549,
2768    19359: 17681,
2769    19363: 17699,
2770    19364: 17627,
2771    19368: 17679,
2772    19369: 17720,
2773    19371: 17675,
2774    19376: 17737,
2775    19387: 17734,
2776    19422: 17480,
2777    19423: 17488,
2778    19424: 17719,
2779    19425: 17489,
2780    19426: 17651,
2781    19429: 17713,
2782    19430: 17641,
2783    19431: 17492,
2784    19432: 17706,
2785    19436: 17669,
2786    19437: 17540,
2787    19442: 17733,
2788    19443: 17647,
2789    19444: 17723,
2790    19445: 17526,
2791    19447: 17542,
2792    19448: 17591,
2793    19450: 17605,
2794    19451: 17648,
2795    19452: 17660,
2796    19511: 17668,
2797    19602: 17715,
2798    19607: 17485,
2799    19608: 17696,
2800    19631: 17518,
2801    19642: 17743,
2802    19649: 17493,
2803    19651: 17708,
2804    19652: 17739,
2805    19653: 17716,
2806    19667: 17711,
2807    19672: 17539,
2808    19680: 17532,
2809    19681: 17655,
2810    19688: 17729,
2811    19867: 17731,
2812    19898: 17671,
2813    19951: 17717,
2814    19954: 17738,
2815    19970: 17495,
2816    20059: 17531,
2817    20074: 17718,
2818    20099: 17741
2819}
2820//players
2821function loadPlayers() {
2822    return new Promise(function (resolve, reject) {
2823        var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerLists/playerList.json`;
2824        getJSON(url, function (result) {
2825            jsonPlayerListArr = result;
2826            setupPlayers();
2827            loadDepthCharts();
2828            resolve('success');
2829        });
2830    });
2831}
2832function loadIDPs() {
2833    return new Promise(function (resolve, reject) {
2834        if (settings.isIDP) {
2835            var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerLists/idpPlayerList.json`;
2836            getJSON(url, function (result) {
2837                jsonPlayerListArr = [...jsonPlayerListArr, ...result];
2838                var playerID = 0;
2839                var arr = result;
2840                var count = arr.length;
2841                for (var i = 0; i < count; i++) {
2842                    playerID = arr[i][0];
2843                    if (playersObj[playerID]) {
2844                        console.log(playerID);
2845                    } else {
2846                        playersObj[playerID] = new PlayerObj(arr[i][1], arr[i][2], arr[i][3], arr[i][4], byeWeeks[arr[i][4]]);
2847                        playersObj[playerID]['isIDP'] = true;
2848
2849                        //Setup display name
2850                        playersNames[playerID] = window.outerWidth > 768 ? arr[i][1] + ' ' + arr[i][2] : arr[i][1].substring(0, 1) + '. ' + arr[i][2];
2851                        playersLastNames[playerID] = arr[i][1].substring(0, 1) + '. ' + arr[i][2];
2852                        playersShortNames[playerID] = arr[i][1].substring(0, 1) + '. ' + arr[i][2];
2853
2854                        //points by source
2855                        playersPoints[playerID] = 0;
2856
2857                        //rankings by source
2858                        //playersPosRankingBySource[playerID] = {};
2859
2860                        //adp by site
2861                        playersADP[playerID] = null;
2862                        //playersADPBySite[playerID] = {};
2863
2864                        //Add to playerSearchArr
2865                        playerSearchArr.push({ player: playersObj[playerID].NameObj.Player, first: playersObj[playerID].NameObj.First, last: playersObj[playerID].NameObj.Last, team: playersObj[playerID].Team, pos: playersObj[playerID].Pos, playerID: playerID });
2866                    }                    
2867                }
2868                resolve('success');
2869            });
2870        } else {
2871            resolve('success');
2872        }
2873    });
2874}
2875
2876function setupPlayers() {
2877    var playerID = 0;
2878    var arr = jsonPlayerListArr;
2879    var count = arr.length;
2880    for (var i = 0; i < count; i++) {
2881        playerID = arr[i][0];
2882        playersObj[playerID] = new PlayerObj(arr[i][1], arr[i][2], arr[i][3], arr[i][4], byeWeeks[arr[i][4]]);
2883
2884        //Setup display name
2885        playersNames[playerID] = window.outerWidth > 768 ? arr[i][1] + ' ' + arr[i][2] : arr[i][1].substring(0, 1) + '. ' + arr[i][2];
2886        playersLastNames[playerID] = arr[i][2];
2887        playersShortNames[playerID] = arr[i][1].substring(0, 1) + '. ' + arr[i][2];
2888
2889        //projected stats by source
2890        playersStats[playerID] = {};
2891        loadedStatsBySource[playerID] = {};
2892
2893        //points by source
2894        playersPoints[playerID] = 0;
2895
2896        //rankings by source
2897        playersPosRankingBySource[playerID] = {};
2898
2899        //adp by site
2900        playersADP[playerID] = null;
2901        playersADPBySite[playerID] = {};
2902
2903        //Add to playerSearchArr
2904        playerSearchArr.push({ player: playersObj[playerID].NameObj.Player, first: playersObj[playerID].NameObj.First, last: playersObj[playerID].NameObj.Last, team: playersObj[playerID].Team, pos: playersObj[playerID].Pos, playerID: playerID });
2905    }
2906    //Add skipped pic
2907    playerID = 0;
2908    playersObj[playerID] = new PlayerObj('Skipped', 'Pick', 'NA', 'NA', 'NA');
2909    playersNames[playerID] = 'Skipped Pick';
2910    playersLastNames[playerID] = 'Pick';
2911    playersShortNames[playerID] = 'Skipped Pick';
2912    
2913}
2914function resetPlayers() {
2915    var playerID = 0;
2916    var arr = jsonPlayerListArr;
2917    var count = arr.length;
2918    for (var i = 0; i < count; i++) {
2919        playerID = arr[i][0];
2920
2921        //projected stats by source
2922        playersStats[playerID] = {};
2923        loadedStatsBySource[playerID] = {};
2924
2925        //points by source
2926        playersPoints[playerID] = 0;
2927
2928        //rankings by source
2929        playersPosRankingBySource[playerID] = {};
2930
2931        //adp by site
2932        playersADP[playerID] = null;
2933        playersADPBySite[playerID] = {};
2934    }
2935}
2936
2937//projections
2938function loadProjections() {
2939    var arr = ['Offense', 'Kickers', 'Defense'], stats = [], playerID = 0, src = '';
2940    sourcesLoaded = 0;
2941    playersStatsBySource = {};
2942    sourcesCount = subscription.projections.length;
2943    return new Promise(function (resolve, reject) {
2944        for (var s = 0; s < sourcesCount; s++) {
2945            src = subscription.projections[s];
2946            playersStatsBySource[src] = {};
2947            for (var i = 0; i < arr.length; i++) {
2948                var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/projections/${src}.${arr[i]}
2948.json?version=${dhversion}`;
2949                fetch(url)
2950                    .then(response => response.json())
2951                    .then(data => {
2952                        var projections = data.projections;
2953                        var source = data.source;
2954                        var type = data.type;
2955                        var cat = type + 'Arr';
2956                        count = projections.length;
2957                        fields = data.statCategories;// statCategories[cat][source];
2958                        fieldsCount = fields.length;
2959                        for (var j = 0; j < count; j++) {
2960                            stats = projections[j];
2961                            playerID = stats[0];
2962                            if (loadedStatsBySource[playerID]) {
2963                                var obj = {};
2964                                for (var k = 1; k < fieldsCount; k++) {//0 is ID so start with 1
2965                                    if (stats[k] > 0) obj[fields[k]] = stats[k];
2966                                }
2967                                loadedStatsBySource[playerID][source] = obj;
2968                            }
2969                        }
2970                        sourcesLoaded++;
2971                        if (sourcesLoaded === sourcesCount * 3) {
2972                            for (playerID in loadedStatsBySource) {
2973                                var sourceObj = loadedStatsBySource[playerID];
2974                                for (source in sourceObj) {
2975                                    playersStatsBySource[source][playerID] = sourceObj[source];
2976                                }
2977                            }
2978                            resolve('success');
2979                        }
2980                    })
2981                    .catch((error) => {
2982                        console.log('loadProjections Error:', error);
2983                    });
2984            }
2985        }
2986        //load IDP
2987        //if (settings.isIDP) {
2988        //    url = 'https://drafthero.blob.core.windows.net/$web/2024/projections/IDP.Points.json?version=' + dhversion;
2989        //    fetch(url)
2990        //        .then(response => response.json())
2991        //        .then(data => {
2992        //            var players = data.fpts;
2993        //            for (playerID in players) {
2994        //                if (playersObj[playerID]) {
2995        //                    playersPoints[playerID] = players[playerID];
2996        //                    playersADPBySite[playerID] = {};
2997        //                    playersADP[playerID] = null;
2998        //                } else {
2999        //                    console.log(playerID);
3000        //                }
3001        //            }
3002        //            resolve('success');
3003        //        })
3004        //        .catch((error) => {
3005        //            console.log('load IDP Points Error:', error);
3006        //        });
3007        //}
3008    });
3009}
3010/*****************Notes**************
3011    Load Player Stats
3012************************************/
3013var addDrops = { 'Adds': {}, 'Drops': {}};
3014function loadPlayerStats() {
3015    return new Promise(function (resolve, reject) {
3016        //load settings
3017        loadHistoricalStats(4).then(function (result) {//Number of years to load
3018            if (result === 'success') {
3019                console.log('loadHistoricalStats successful');
3020                loadHistoricalPosRank(4).then(function (result) {
3021                    if (result === 'success') {
3022                        console.log('loadHistoricalPosRank successful');
3023                        loadHistoricalWeeklyPosRank().then(function (result) {
3024                            if (result === 'success') {
3025                                console.log('loadHistoricalWeeklyPosRank successful');
3026                                //load add drops
3027                                loadAddDrops().then(function (result) {
3028                                    if (result === 'success') {
3029                                        console.log('loadADP successful');
3030                                        resolve('success');
3031                                    }
3032                                }, function (reject) {//error on loadADP
3033                                    azureError(reject).then(function (result) {
3034                                        resolve(result);
3035                                    });
3036                                });
3037                            }
3038                        }, function (reject) {//error on loadProjections
3039                            azureError(reject).then(function (result) {
3040                                resolve(result);
3041                            });
3042                        });
3043                    }
3044                }, function (reject) {//error on loadDraftPicks
3045                    azureError(reject).then(function (result) {
3046                        resolve(result);
3047                    });
3048                });
3049            }
3050        }, function (reject) {//error on loadSettings
3051            azureError(reject).then(function (result) {
3052                resolve(result);
3053            });
3054        });
3055    });
3056}
3057function loadHistoricalStats(count) {
3058    return new Promise(function (resolve, reject) {
3059        var year = 0, sourcecount = 0;
3060        for (var i = 1; i <= count; i++) {
3061            year = currentSeason - i;
3062            //https://drafthero2020.blob.core.windows.net/$web/2020/actual/season.stats.json
3063            var url = 'https://drafthero.blob.core.windows.net/$web/' + year + '/actual/season.stats.json';
3064            getJSONbySource(url, { 'year': year }, function (result) {
3065                histStats.seasonStats[result.source.year] = result.json;
3066                sourcecount++;
3067                if (sourcecount === count) {
3068                    //Remove playerIDs from playersStats with no projections
3069                    //for (playerid in playersStats) {
3070                    //    if (Object.keys(playersStats[playerid]).length === 0) {
3071                    //        delete playersStats[playerid];
3072                    //    }
3073                    //}
3074                    resolve('success');
3075                }
3076            });
3077        }
3078    });
3079}
3080function loadHistoricalPosRank(count) {
3081    return new Promise(function (resolve, reject) {
3082        var year = 0, sourcecount = 0;
3083        for (var i = 1; i <= count; i++) {
3084            year = currentSeason - i;
3085            var url = 'https://drafthero.blob.core.windows.net/$web/' + year + '/actual/season.posRank.json';
3086            getJSONbySource(url, { 'year': year }, function (result) {
3087                histStats.posRank[result.source.year] = result.json;
3088                sourcecount++;
3089                if (sourcecount === count) {
3090                    //Remove playerIDs from playersStats with no projections
3091                    //for (playerid in playersStats) {
3092                    //    if (Object.keys(playersStats[playerid]).length === 0) {
3093                    //        delete playersStats[playerid];
3094                    //    }
3095                    //}
3096                    resolve('success');
3097                }
3098            });
3099        }
3100    });
3101}
3102function loadHistoricalWeeklyPosRank() {
3103    return new Promise(function (resolve, reject) {
3104        var sourcecount = 0;
3105        year = currentSeason - 1;
3106        //Weekly summary
3107        var url = 'https://drafthero.blob.core.windows.net/$web/' + year + '/actual/weekly.posRank.summary.json';
3108        getJSONbySource(url, {}, function (result) {
3109            histStats.posRankingsSummary = result.json;
3110            sourcecount++;
3111            if (sourcecount === 2) {
3112                resolve('success');
3113            }
3114        });
3115        //Weekly detail
3116        url = 'https://drafthero.blob.core.windows.net/$web/' + year + '/actual/weekly.posRank.detail.json';
3117        getJSONbySource(url, {}, function (result) {
3118            histStats.posRankingsWeekly = result.json;
3119            sourcecount++;
3120            if (sourcecount === 2) {
3121                resolve('success');
3122            }
3123        });
3124    });
3125}
3126function loadPlayerNotes(playerID) {
3127    return new Promise(function (resolve, reject) {
3128        var url = 'https://drafthero.blob.core.windows.net/$web/' + currentSeason + '/playerNotes/4for4/' + playerID + '.json';
3129        getJSONbySource(url, { 'playerID': playerID }, function (result) {
3130            playerNotes['4for4'][playerID] = result.json.Note;
3131            resolve('success');
3132        });
3133    }).then(function (result) {
3134        return result;
3135    }).catch(function (error) {
3136        return error;
3137    });
3138}
3139function loadPlayerNews(playerID) {
3140    return new Promise((resolve, reject) => {
3141        var obj = { 'email': user.email, 'playerid': +playerID, 'code': gcode };
3142        post(obj, '/getPlayerNews', function (arr) {
3143            playerNews[playerID] = [];
3144            for (var i = 0; i < arr.length; i++) {
3145                playerNews[playerID].push(JSON.parse(arr[i].data));
3146            }
3147            resolve('success');
3148        });
3149    }).then(function (result) {
3150        return result;
3151    }).catch(function (error) {
3152        return error;
3153    });
3154}
3155function loadAddDrops() {
3156    return new Promise(function (resolve, reject) {
3157        //Adds
3158        var url = 'https://drafthero.blob.core.windows.net/$web/' + currentSeason + '/inseason/addDrops.Adds.json';
3159        getJSON(url, function (result) {
3160            addDrops[result.type] = result.players;
3161            //Drops
3162            url = 'https://drafthero.blob.core.windows.net/$web/' + currentSeason + '/inseason/addDrops.Drops.json';
3163            getJSON(url, function (result) {
3164                addDrops[result.type] = result.players;
3165                console.log('add drops loaded');
3166                resolve('success');
3167            });
3168        });
3169    });
3170}
3171function getPointsByPlayer(pos, stats) {//get points for historical stats
3172    var points = 0, stat = null, statCategory = '';
3173    var statCategories = ['PAYD', 'PATD', 'PAINT', 'RUYD', 'RUTD', 'REC', 'REYD', 'RETD'];
3174    var statsObj = { 'PAYD': 0, 'PATD': 0, 'PAINT': 0, 'RUYD': 0, 'RUTD': 0, 'REC': 0, 'REYD': 0, 'RETD': 0 };
3175    var scoringRules = settings.Scoring.All;
3176    var hasPosScoringRules = settings.Scoring.Positional;
3177    var posScoringRules = settings.Scoring.Positional;
3178
3179    if (hasPosScoringRules) {
3180        var keys = Object.keys(posScoringRules);
3181        var keysCount = keys.length;
3182    }
3183
3184    //Loop through stats for player
3185    if (stats) {
3186        for (var i = 0; i < 8; i++) {
3187            statCategory = statCategories[i];
3188            if (scoringRules[statCategory]) points += stats[i] * scoringRules[statCategory];
3189            statsObj[statCategory] = stats[i];//Save for use below
3190        }
3191        //Loop through positional rules
3192        if (hasPosScoringRules) {
3193            if (pos === 'RB' || pos === 'WR' || pos === 'TE') {
3194                for (var j = 0; j < keysCount; j++) {
3195                    stat = keys[j];
3196                    if (posScoringRules[stat][pos]) points += ~~(statsObj[stat] * (posScoringRules[stat][pos] - (scoringRules[stat] || 0)));
3197                }
3198            }
3199        }
3200    }
3201
3202    return points;
3203
3204}
3205/*****************Notes**************
3206    Setup Core Analytics
3207************************************/
3208
3209function setupCoreAnalytics() {//These analytics are updated on setup
3210    analyticsObj = new AnalyticsObj();
3211    setupBaselineValuesObj();
3212    setupVBD();
3213}
3214function setupBaselineValuesObj() {//Improve for Flex Positions
3215    //Determine Baseline Values
3216    var baselineValuesObj = { Starters: { 'QB': 0, 'RB': 0, 'WR': 0, 'TE': 0, 'DT': 0, 'PK': 0 }, Total: { 'QB': 0, 'RB': 0, 'WR': 0, 'TE': 0, 'DT': 0, 'PK': 0 } };
3217    var flexStartersObj = { 'QB': 1, 'RB': 1, 'WR': 1, 'TE': 1 };
3218    var teamCount = settings.Teams.Size, rosterSettings = settings.Rosters.Pos, rosterFlexSettings = settings.Rosters.Flex;
3219    var _playersPoints = playersPoints;
3220    var posRank = null, playerID = null, pos = '', flexCount = 0;
3221
3222    for (i = 0; i < 6; i++) {
3223        pos = posListArr[i];
3224        if (rosterSettings[pos]) {
3225            if (rosterSettings[pos].Starters > 0) {
3226                //flex
3227                flexCount = 0;
3228                for (flex in rosterFlexSettings) {
3229                    if (flex.indexOf(pos) > -1) {
3230                        switch (pos) {
3231                            case 'QB':
3232                                flexCount = rosterFlexSettings[flex].Starters;
3233                                break;
3234                            case 'RB':
3235                            case 'WR':
3236                                flexCount = rosterFlexSettings[flex].Starters / 2;
3237                                break;
3238                            case 'TE':
3239                                flexCount = rosterFlexSettings[flex].Starters / teamCount;
3240                                break;
3241                        }
3242                    }
3243                }
3244                //Starters
3245                posRank = (rosterSettings[pos].Starters + flexCount) * teamCount;
3246                posRank = Math.floor(posRank);
3247                playerID = posRankings[pos][posRank - 1];
3248                baselineValuesObj.Starters[pos] = _playersPoints[playerID] || 0;
3249
3250                //Total
3251                posRank = (rosterSettings[pos].Starters + rosterSettings[pos].Bench + flexCount) * teamCount;
3252                playerID = posRankings[pos][posRank - 1];
3253                baselineValuesObj.Total[pos] = _playersPoints[playerID] || 0;
3254            } else if (flexStartersObj[pos]) {
3255                //flex
3256                flexCount = 0;
3257                for (flex in rosterFlexSettings) {
3258                    if (flex.indexOf(pos) > -1) {
3259                        switch (pos) {
3260                            case 'QB':
3261                                flexCount = rosterFlexSettings[flex].Starters;
3262                                break;
3263                            case 'RB':
3264                            case 'WR':
3265                                flexCount = rosterFlexSettings[flex].Starters / 2;
3266                                break;
3267                            case 'TE':
3268                                flexCount = rosterFlexSettings[flex].Starters / 4;
3269                                break;
3270                        }
3271                    }
3272                }
3273
3274                //Starters
3275                posRank = teamCount * flexCount;
3276                playerID = posRankings[pos][posRank - 1];
3277                baselineValuesObj.Starters[pos] = _playersPoints[playerID] || 0;
3278
3279                //Total
3280                posRank = teamCount * (flexCount + 2);//added 2 arbitrary
3281                playerID = posRankings[pos][posRank - 1];
3282                baselineValuesObj.Total[pos] = _playersPoints[playerID] || 0;
3283            }
3284        }
3285    }
3286    analyticsObj.BaselineValuesObj = baselineValuesObj;
3287}
3288function setupVBD() {
3289    var points = 0, pos = null;
3290    var baselineValuesObj = analyticsObj.BaselineValuesObj;
3291    var _playersObj = playersObj;
3292    var _playersPoints = playersPoints;
3293    var arv = 0;
3294    //Loop through players
3295    for (playerID in _playersPoints) {
3296        pos = _playersObj[playerID].Pos;
3297        points = _playersPoints[playerID];
3298
3299        arv = points - baselineValuesObj.Starters[pos];
3300        switch (true) {
3301            case arv > 0:
3302                playersARV[playerID] = `+${arv}`;
3303                break;
3304            case arv < 0:
3305                playersARV[playerID] = arv;
3306                break;
3307            default:
3308                playersARV[playerID] = `--`;
3309                break;
3310        }
3311    }
3312}
3313
3314//blend
3315function setupBlend(customBlend) {
3316    var equal = (100 / sourcesCount) / 100;
3317    var source = '';
3318    blendObj = {};
3319
3320    if (customBlend !== undefined) {
3321        for (var i = 0; i < subscription.projections.length; i++) {
3322            source = subscription.projections[i];
3323            blendObj[source] = { Licensed: true, Blend: customBlend[source] || 0 };
3324        }
3325    } else {
3326        for (i = 0; i < subscription.projections.length; i++) {
3327            source = subscription.projections[i];
3328            blendObj[source] = { Licensed: true, Blend: equal };
3329        }
3330    }
3331
3332    blendStats();
3333    updatePlayersStats();
3334}
3335function blendStats() {
3336    var source = null, sourcesArr = [], playerObj = {}, blendedValuesArr = [];
3337    var blendTotal = null;
3338    var statsObj = {}, statCategory = null, statCategoriesObj = {}, statCategoriesArr = [], statCategoriesCount = null, statValue = null, statValueTotal = null, count = null;
3339
3340    for (var i = 0; i < subscription.projections.length; i++) {
3341        source = subscription.projections[i];
3342        if (blendObj[source] && blendObj[source].Blend > 0) {
3343            sourcesArr.push(source);
3344            blendedValuesArr.push(blendObj[source].Blend);
3345        }
3346    }
3347    count = sourcesArr.length;
3348
3349    //Use Default Stat Categories
3350    var cat = statCategories.OffenseArr.Demo;
3351    statCategoriesObj['QB'] = { 'arr': cat, 'count': cat.length };
3352    statCategoriesObj['RB'] = { 'arr': cat, 'count': cat.length };
3353    statCategoriesObj['WR'] = { 'arr': cat, 'count': cat.length };
3354    statCategoriesObj['TE'] = { 'arr': cat, 'count': cat.length };
3355    statCategoriesObj['DT'] = { 'arr': statCategories.DefenseArr.Demo, 'count': statCategories.DefenseArr.Demo.length };
3356    statCategoriesObj['PK'] = { 'arr': statCategories.KickersArr.Demo, 'count': statCategories.KickersArr.Demo.length };
3357
3358    //Loop through players
3359    playersStatsBySource['Blended'] = {};
3360    for (playerID in playersStats) {
3361        playerObj = playersObj[playerID];
3362
3363        if (posListArr.includes(playerObj.Pos)) {
3364            playersStatsBySource.Blended[playerID] = {};
3365            statCategoriesArr = statCategoriesObj[playerObj.Pos].arr;
3366            statCategoriesCount = statCategoriesObj[playerObj.Pos].count;
3367
3368            //Loop through stat categories
3369            for (var j = 1; j < statCategoriesCount; j++) {
3370                statCategory = statCategoriesArr[j];
3371                statValueTotal = 0;
3372                blendTotal = 0;
3373
3374                //Loop through sources
3375                for (var k = 0; k < count; k++) {
3376                    source = sourcesArr[k];
3377                    statsObj = playersStatsBySource[source][playerID];
3378                    if (statsObj) {
3379                        statValue = statsObj[statCategory];
3380                        if (statValue) {
3381                            statValueTotal += statValue * blendedValuesArr[k];
3382                            blendTotal += blendedValuesArr[k];
3383                        }
3384                    }
3385                }
3386                if (blendTotal > 0) {
3387                    playersStatsBySource.Blended[playerID][statCategory] = ~~(100 * (statValueTotal / blendTotal)) / 100;
3388                }
3389            }
3390        }
3391    }
3392}
3393function updatePlayersStats() {
3394    var stats = playersStatsBySource[selectedSource];
3395    for (playerID in stats) {
3396        playersStats[playerID] = stats[playerID] || {};
3397    }
3398
3399}
3400function setupDraftPicks() {
3401    var picks = getLocal('picks-' + leagueSelected);//json -> obj
3402    if (picks) {
3403        draftPicksObj = picks.draftPicksObj;
3404        myDraftPicksObj = picks.myDraftPicksObj;
3405        myDraftPicksArr = picks.myDraftPicksArr;
3406
3407        for (obj in draftPicksObj) {
3408            let playerid = draftPicksObj[obj].pID;
3409            if (playersObj[playerid]) {
3410                playersDrafted[playerid] = true;
3411            }            
3412        }
3413        //add Keepers, if any
3414        //if (settings.Draft.Keepers !== undefined) setupKeepers();
3415    }
3416}
3417/*****************Notes**************
3418    Setup Fantasy Points
3419************************************/
3420function setupPoints() {
3421    calcPlayersPoints();
3422    updatePlayersPoints();
3423    if (window.location.pathname === '/draft' && selectedSource === 'Custom') setupCustomPoints();
3424}
3425function calcPlayersPoints() {
3426    var points = null, stat = null, stats = {}, source = null, playerID = null, pos = null, rulePos = '', posRule = null;
3427    var statCategories = {}, statCategoriesCount = 0;
3428    var hasPosScoringRules = false;
3429    var posScoringRules = {};
3430    var _playersObj = playersObj;
3431    
3432    //loop through each licensed data source; add blended for subscribers only
3433    var sourcesArr = ['Blended'];
3434    
3435    for (var i = 0; i < subscription.projections.length; i++) {
3436        source = subscription.projections[i];
3437        sourcesArr.push(source);
3438        playersPointsBySource[source] = {};
3439    }
3440
3441    //Positional scoring
3442    for (rule in settings.Scoring.Positional) {
3443        hasPosScoringRules = true;
3444        posScoringRules = settings.Scoring.Positional;
3445        var keys = Object.keys(posScoringRules);
3446        var keysCount = keys.length;
3447    }
3448
3449    //Scoring Rules
3450    var scoringRules = settings.Scoring.All;
3451
3452    //Loop through players
3453    playersPointsBySource['Blended'] = {};
3454    for (playerID in playersStats) {
3455        playerID = ~~playerID;
3456        //loop through each licensed data source
3457        for (var s = 0; s < sourcesArr.length; s++) {
3458            source = sourcesArr[s];
3459            playersPointsBySource[source][playerID] = {};
3460            stats = playersStatsBySource[source][playerID];
3461            points = 0;
3462
3463            //Loop through stats for each player
3464            if (stats) {
3465                statCategories = Object.keys(stats);
3466                statCategoriesCount = statCategories.length;
3467                for (var statKey = 0; statKey < statCategoriesCount; statKey++) {
3468                    stat = statCategories[statKey];
3469                    if (scoringRules[stat]) points += stats[stat] * scoringRules[stat];
3470                }
3471                //Loop through positional rules
3472                if (hasPosScoringRules) {
3473                    pos = _playersObj[playerID].Pos;
3474                    if (pos === 'RB' || pos === 'WR' || pos === 'TE') {
3475                        for (i = 0; i < keysCount; i++) {
3476                            posRule = keys[i];
3477                            stat = posRule.split('-')[0];
3478                            rulePos = posRule.split('-')[1];
3479                            if (stat && rulePos && rulePos === pos) {
3480                                if (scoringRules[stat]) {
3481                                    if (posScoringRules[posRule] && scoringRules[stat]) points += ~~(stats[stat] * (posScoringRules[posRule] - (scoringRules[stat] || 0)));
3482                                } else {
3483                                    if (posScoringRules[posRule]) points += ~~(stats[stat] * posScoringRules[posRule]);
3484                                }
3485                                
3486                            }
3487                        }
3488                    }
3489                }
3490                //Update Points for player for source
3491                playersPointsBySource[source][playerID] = ~~points;
3492            } else {//No stats, zero points
3493                playersPointsBySource[source][playerID] = 0;
3494            }
3495        }
3496    }
3497
3498    if (window.location.pathname === '/draft' && userDraftOptions.customPoints) playersPointsBySource['Custom'] = userDraftOptions.customPoints;
3499}
3500function updatePlayersPoints() {
3501    var obj = playersPointsBySource[selectedSource];
3502    for (playerID in obj) {
3503        playersPoints[playerID] = obj[playerID] || 0;
3504    }
3505    //use 4for4 FPTS for defenses
3506    //setupPointsDEF();
3507    if (settings.Options.useDefenseRankings) {
3508        defenseRankingsArr = getDefenseRankings();
3509    }
3510}
3511function setupPointsDEF() {
3512    var playerID = 0;
3513
3514    for (playerID in playersPoints) {
3515        if (playersObj[playerID].Pos === 'DT') {
3516            playersPoints[playerID] = ~~playersStatsBySource.Blended[playerID].FPTS;
3517            if (playersPointsBySource.Blended[playerID]) {//update blended as well for rankings
3518                playersPointsBySource.Blended[playerID] = playersPoints[playerID];
3519            }
3520        } else {
3521            break;
3522        }
3523
3524    }
3525}
3526function getDefenseRankings() {
3527    const defenses = [];
3528
3529    for (const playerID in playersStatsBySource.Blended) {
3530        if (playersObj[playerID].Pos === 'DT') {
3531            defenses.push({
3532                playerID,
3533                rank: ~~playersStatsBySource.Blended[playerID].Rank
3534            });
3535        }
3536    }
3537
3538    // Sort defenses by ascending rank
3539    defenses.sort((a, b) => a.rank - b.rank);
3540
3541    return defenses.map(def => Number(def.playerID));
3542}
3543
3544function setupCustomPoints() {
3545    var playerID = null;
3546
3547    //Add custom points if any
3548    if (userDraftOptions.customPoints) {
3549        var customPointsObj = userDraftOptions.customPoints;
3550        //Loop through players
3551        for (playerID in customPointsObj) {
3552            if (playersObj[playerID]) {
3553                playersPoints[playerID] = customPointsObj[playerID] || 0;
3554            }
3555        }
3556    }
3557}
3558
3559//model player rankings
3560function setupPlayersRankings() {
3561    setupPlayersRankingsBySource();
3562    setupRankingsByPositionBySource();
3563    updatePlayersRankings();
3564}
3565function setupPlayersRankingsBySource() {
3566    playersRankingsBySource = {};
3567    var points = null, count = 0, playerID = null;
3568
3569    var sortableArr = [];
3570    var playersPoints = {}
3571    //Loop through players
3572
3573    for (source in playersPointsBySource) {
3574        sortableArr = [];
3575        playersPoints = playersPointsBySource[source];
3576        for (playerID in playersPoints) {
3577            points = playersPoints[~~playerID];
3578            if (points > 0) sortableArr.push({ 'ID': ~~playerID, 'Points': points });
3579        }
3580        sortableArr.sort(function (a, b) { return b.Points - a.Points; });
3581
3582        //Store PlayerIDs
3583        count = sortableArr.length;
3584        var playersArr = new Array(count);
3585        for (var i = 0; i < count; i++) {
3586            playersArr[i] = sortableArr[i].ID;
3587        }
3588
3589        playersRankingsBySource[source] = playersArr;
3590    }
3591}
3592function setupRankingsByPositionBySource() {
3593    var source = '', rankings = [], count = 0, playerID = 0, pos = '', posRank = 0;
3594    var posRankObj = {};
3595    var _posRankingsBySource = {};
3596
3597    for (source in playersRankingsBySource) {
3598        rankings = playersRankingsBySource[source];
3599        //console.dir(rankings);
3600        count = rankings.length;
3601
3602        posRankObj = { 'QB': 1, 'RB': 1, 'WR': 1, 'TE': 1, 'DT': 1, 'PK': 1 };
3603        _posRankingsBySource = { 'QB': [], 'RB': [], 'WR': [], 'TE': [], 'DT': [], 'PK': [] };
3604
3605        for (var i = 0; i < count; i++) {
3606            playerID = rankings[i];
3607            pos = playersObj[playerID].Pos;
3608            posRank = posRankObj[pos];
3609
3610            playersPosRankingBySource[playerID][source] = posRank;            
3611            _posRankingsBySource[pos].push(playerID);
3612
3613            posRankObj[pos]++;
3614        }
3615        posRankingsBySource[source] = _posRankingsBySource;
3616    }
3617}
3618function updatePlayersRankings() {
3619    //Array of playerIDs ranked by points
3620    playersRankings = playersRankingsBySource[selectedSource];
3621
3622    //use ADP for dynasty leagues
3623    if (settings.Draft['Advanced Options'] && settings.Draft['Advanced Options'].includes('Dynasty')) {
3624        //playersRankings = playersRankedByADP;
3625        const kickersRanking = [
3626            17649, 8398, 11031, 11783, 17133, 16226, 12203, 13673, 13007, 10157, 14609,
3627            12292, 9633, 8896, 15383, 12292, 9633, 12842, 17595, 17376, 17926, 17922,
3628            5051, 11309, 8179, 14254, 13266, 9054, 9948, 5365, 9646, 12582, 16646, 12775,
3629            6045, 17165, 15512, 14172, 14189
3630        ]
3631        const defensesRanking = [
3632            3, 8, 27, 9, 21, 15, 24, 16, 4, 17, 23, 6, 19, 32, 10, 12, 7, 13, 14, 35,
3633            18, 11, 21, 5, 33, 26, 29, 34, 30, 2
3634        ];
3635        // Use a Set to combine the arrays and ensure uniqueness
3636        const combinedSet = new Set([...playersRankedByADP, ...kickersRanking, ...defensesRanking]);
3637        // Convert the Set back to an array
3638        playersRankings = Array.from(combinedSet);
3639    } else {
3640        playersRankings = playersRankingsBySource[selectedSource];
3641    }
3642
3643    //Object of sources with array of playerIDs ranked by pos
3644    posRankings = posRankingsBySource[selectedSource];
3645
3646    //Object of playerID : posRank
3647    for (playerID in playersPosRankingBySource) {
3648        playersPosRanking[playerID] = playersPosRankingBySource[playerID].Blended;
3649    }
3650    
3651
3652}
3653function addtoDynastyRankings() {
3654    const kickersRanking = [
3655        17649, 8398, 11031, 11783, 17133, 16226, 12203, 13673, 13007, 10157, 14609,
3656        12292, 9633, 8896, 15383, 12292, 9633, 12842, 17595, 17376, 17926, 17922,
3657        5051, 11309, 8179, 14254, 13266, 9054, 9948, 5365, 9646, 12582, 16646, 12775,
3658        6045, 17165, 15512, 14172, 14189
3659    ]
3660    // Use a Set to combine the arrays and ensure uniqueness
3661    const combinedSet = new Set([...playersRankedByADP, ...kickersRanking]);
3662
3663    // Convert the Set back to an array
3664    playersRankings = Array.from(combinedSet);
3665}
3666
3667
3668//model
3669function setupModel() {
3670    return new Promise(function (resolve, reject) {
3671        validateSettings();
3672        if (settings.Options && settings.Options.CustomBlend) {
3673            setupBlend(settings.Options.CustomBlend);
3674        } else {
3675            setupBlend();
3676        }
3677        setupADP(settings.Host.Site);
3678        setupPoints();
3679        checkDND();
3680        setupPlayersRankings();
3681        setupCoreAnalytics();
3682        createTiers();
3683        setupDraftPicks();
3684        resolve('success');
3685    });
3686}
3687function checkDND() {
3688    var percent = 0;
3689    for (playerID in dndList.Points) {
3690        percent = dndList.Points[playerID];
3691        if (playersObj[playerID]) {
3692            for (source in playersPointsBySource) {
3693                playersPointsBySource[source][playerID] = ~~(playersPointsBySource[source][playerID] * percent);
3694            }
3695            playersPoints[playerID] = ~~(playersPoints[playerID] * percent);
3696        }
3697    }
3698}
3699function validateSettings() {
3700    //check for null in draft order
3701    var header = '<img class="img-icon" src="../img/logo/icon.webp" alt="Draft Hero Shield"> UPDATING SETTINGS';
3702    var resetDraftOrder = false;
3703    var teamid = '';
3704    for (order in settings.Draft.Order) {
3705        teamid = settings.Draft.Order[order];
3706        if (teamid === 'null' || teamid === null || !settings.Teams.Names[teamid]) {
3707            resetDraftOrder = true;
3708            break;
3709        }
3710    }
3711    //check for missing teamids in draft order
3712    if (resetDraftOrder) {
3713        var draftOrder = [], order = 1;
3714        var draftOrderByTeamID = {};
3715        for (teamid in settings.Teams.Names) {
3716            draftOrder.push(teamid);
3717            draftOrderByTeamID[teamid] = order;
3718            order++;
3719        }
3720        settings.Draft.Order = draftOrder;
3721        settings.Draft.OrderByTeamID = draftOrderByTeamID;
3722
3723        openModal('Something was wrong with your Draft Order so we have updated your settings.  If this continues, please send an email to [email protected] so we can investigate further. Thank you.', header, 'Save Updated Settings', function () {
3724            saveLeague();
3725        });
3726    }
3727
3728    //temp check for myteam.id integer
3729    if (settings.MyTeam.ID === Number(settings.MyTeam.ID)) {
3730        settings.MyTeam.ID = settings.MyTeam.ID.toString();
3731        saveLocal('settings-' + leagueSelected, settings);
3732        openModal('Something was wrong with your Team ID, so we have updated your settings.  If this continues, please send an email to [email protected] so we can investigate further. Thank you.', header, 'Save Updated Settings', function () {
3733            saveLeague();
3734        });
3735    }
3736    //temp check for myteam draft position
3737    if (settings.MyTeam.DraftPosition === undefined) {
3738        settings.MyTeam.DraftPosition = settings.Draft.OrderByTeamID[settings.MyTeam.ID];
3739        saveLocal('settings-' + leagueSelected, settings);
3740        saveLeague();
3741        openModal('Your team did not have a draft position, so we added it and saved your updated settings. Please confirm your draft position is correct.  If this continues, please send an email to [email protected] so we can investigate further. Thank you.', header, null, null);
3742    }
3743
3744    //check custom settings errors or nulls
3745    if (settings.Options.ADP === 'null') {
3746        settings.Options.ADP = settings.Host.Site;
3747        saveLocal('settings-' + leagueSelected, settings);
3748        header = '<img class="img-icon" src="../img/logo/icon.webp" alt="Draft Hero Shield"> UPDATING CUSTOM SETTINGS';
3749        openModal('Something was wrong with your custom settings for ADP, so we have updated your settings.  If this continues, please send an email to supp
3749[email protected] so we can investigate further. Thank you.', header, 'Save Updated Settings', function () {
3750            saveLeague();
3751        });
3752    }
3753    //check for null in settings.Rosters
3754    delete settings.Rosters.null;
3755}
3756
3757/*****************Notes**************
3758    Tiers
3759************************************/
3760function createTiers() {
3761    var playersByPos = { 'QB': [], 'RB': [], 'WR': [], 'TE': [], 'PK': [], 'DT': [] };
3762    var pos = '', playerID = 0, gap = 0;
3763    var lastPointsByPos = { 'QB': null, 'RB': null, 'WR': null, 'TE': null, 'PK': null, 'DT': null }, points = 0;
3764    var tierCountByPos = { 'QB': 1, 'RB': 1, 'WR': 1, 'TE': 1, 'PK': 1, 'DT': 1 };
3765    var countByPos = { 'QB': 24, 'RB': 36, 'WR': 36, 'TE': 16, 'PK': 16, 'DT': 16 };
3766    var tiersByPos = { 'QB': 7, 'RB': 9, 'WR': 10, 'TE': 7, 'PK': 7, 'DT': 7 };
3767
3768    //determine gaps
3769    var count = playersRankings.length;
3770    for (var i = 0; i < count; i++) {
3771        playerID = playersRankings[i];
3772        pos = playersObj[playerID].Pos;
3773        points = playersPoints[playerID];
3774        if (lastPointsByPos[pos] !== null) {
3775            gap = lastPointsByPos[pos] - points;
3776        } else {
3777            gap = 0;
3778        }
3779        playersByPos[pos].push({ 'playerID': playerID, 'points': points, 'gap': gap });
3780        lastPointsByPos[pos] = points;
3781    }
3782
3783    //Sort by gap desc
3784    var newTier = [];
3785    for (pos in playersByPos) {
3786        playersByPos[pos].sort(function (a, b) { return b.gap - a.gap; });
3787
3788        for (i = 0; i < playersByPos[pos].length; i++) {
3789            playerID = playersByPos[pos][i].playerID;
3790            if (playersPosRanking[playerID] < countByPos[pos]) {
3791                if (tiersByPos[pos] > 0) {
3792                    newTier.push(playerID);
3793                    tiersByPos[pos]--;
3794                }
3795            } else if (i < 2) {
3796                newTier.push(playerID);
3797            }
3798
3799        }
3800    }
3801
3802    //create tiers for largest gaps
3803    for (i = 0; i < count; i++) {
3804        playerID = playersRankings[i];
3805        pos = playersObj[playerID].Pos;
3806        if (newTier.includes(playerID)) {
3807            tierCountByPos[pos]++;
3808        }
3809        playerTiers[playerID] = tierCountByPos[pos];
3810    }
3811}
3812function updatePlayerNotes(playerID) {
3813    document.getElementById('profile-playerOutlook-RotoWire').innerHTML = 'No RotoWire Outlook Available.';//'Outlooks available early July.'
3814    document.getElementById('profile-playerOutlook-4for4').innerHTML = 'No 4for4 Outlook Available.';
3815    //4for4 outlooks
3816    if (playerNotes['4for4'][playerID] === undefined) {
3817        getPlayerNote(playerID, '4for4').then(function (result) {
3818            if (result !== '') {
3819                playerNotes['4for4'][playerID] = result;
3820                viewPlayerNotes(result, "4for4");
3821                //viewPlayerNotes(result.outlook);
3822            }
3823        });
3824    } else {
3825        //viewPlayerNotes(playerNotes['4for4'][playerID].outlook);
3826        viewPlayerNotes(playerNotes['4for4'][playerID], "4for4");
3827    }
3828
3829    //RotoWire outlooks
3830    if (playerNotes['RotoWire'][playerID] === undefined) {
3831        getPlayerNote(playerID, 'RotoWire').then(function (result) {
3832            if (result !== undefined) {
3833                playerNotes['RotoWire'][playerID] = result;
3834                viewPlayerNotes(result, "RotoWire");
3835            }
3836        });
3837    } else {
3838        viewPlayerNotes(playerNotes['RotoWire'][playerID], "RotoWire");
3839    }
3840}
3841function getPlayerNote(playerid, source) {
3842    return new Promise((resolve, reject) => {
3843        var obj = { 'playerid': Number(playerid), 'source': source, 'email': user.email, 'code': gcode };
3844        var html = '';
3845
3846        post(obj, '/getPlayerNote', function (data) {
3847            let outlook = data.outlook;
3848            //let playerObj = playersObj[playerid];
3849            //if (playerObj) {
3850            //    let photo = getPhoto(playerid, 64);
3851            //    html = `<div class="news-item">
3852            //                    <div class="profile" onclick="showPlayer(${playerid})" style="cursor:pointer">
3853            //                        <div>${photo}</div>
3854            //                        <div>
3855            //                            <p class="p1">${playersNames[playerid]}<span class="p5" style="margin-left: .5em">${playerObj.Pos}, ${playerObj.Team}</span> <img src="../../css/sprite.svg" class="svg svg-link" height="10" width="10" style="margin-left:4px" alt="open link icon"></p>
3856            //                            <p class="p3" style="margin-top:.5em">Position Rank: #${playersPosRanking[playerid] || 'nr'} ADP Rank: #${playersADP[playerid]} </p>
3857            //                            <p class="p6" style="margin-top:1em">Published June, 2025</p>
3858            //                        </div>
3859            //                    </div>
3860            //                    <p style="margin-bottom:.5em"><strong>2025 Season Outlook: </strong> "${outlook.outlook}" - RotoWire</a></p>
3861            //                </div>`;
3862            //    resolve(html);
3863            //}
3864            resolve(outlook);
3865        });
3866    }).then(function (result) {
3867        return result;
3868    }).catch(function (error) {
3869        return error;
3870    });
3871}
3872function viewPlayerNotes(content, source) {
3873    //Outlooks
3874    var html = 'No Player Outlook Available.';// 'No Outlook Available.';
3875    if (content) {
3876        switch (source) {
3877            case 'RotoWire':
3878                html = `<span><strong>From RotoWire.com</strong></br>"${content}"</span></br>`;
3879                document.getElementById('profile-playerOutlook-RotoWire').innerHTML = html;
3880                break;
3881            case '4for4':
3882                html = `<span><strong>From <a href="https://twitter.com/4for4_John"><i class="fa-brands fa-twitter"></i>@4for4_John</a> John Paulsen, 4for4.com</strong></br>"${content}"</span>`;
3883                document.getElementById('profile-playerOutlook-4for4').innerHTML = html;
3884                break;
3885        }
3886        
3887    }
3888    //document.getElementById('profile-playerOutlook').innerHTML = html;
3889}
3890function getOutlook(playerid) {
3891    return new Promise(function (resolve, reject) {
3892        var url = `https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerNotes/4for4/${playerid}.json`;
3893        getJSON(url, function (result) {
3894            resolve(result);
3895        });
3896    }).then(function (result) {
3897        return result;
3898    }).catch(function (error) {
3899        return error;
3900    });
3901}
3902
3903function updatePlayerNews(playerID) {
3904    if (user.status === 'subscribed') {
3905        if (playerNews[playerID] === undefined) {
3906            loadPlayerNews(playerID).then(function (result) {
3907                if (result === 'success') viewPlayerNews(playerID);
3908            });
3909        } else {
3910            viewPlayerNews(playerID);
3911        }
3912    } else {
3913        document.getElementById('profile-playerNews').innerHTML = '<em>Subscription required for player news.</em>';
3914    }
3915}
3916function viewPlayerNews(playerID) {
3917    //News
3918    var news = playerNews[playerID];
3919    if (news === undefined || news.length === 0) {
3920        document.getElementById('profile-playerNews').innerHTML = 'No News Available.';
3921    } else {
3922        var sortableArr = [];
3923        for (i = 0; i < news.length; i++) {
3924            sortableArr.push({ "updateid": news[i].updateid, "news": news[i] });
3925        }
3926        sortableArr.sort(function (a, b) { return b.updateid - a.updateid });
3927        var html = '';
3928        for (i = 0; i < sortableArr.length; i++) {
3929            item = sortableArr[i].news;
3930            html += '<div style="margin-bottom:4px"><strong style="text-transform:uppercase">' + item.headline + ' </strong><span><span class="fas fa-clock"></span> ' + item.time + ' ' + item.date + '</span></div>';
3931            html += '<div><strong>News:</strong> ' + item.notes + '</div>';
3932            html += '<div><strong>Fantasy Analysis:</strong> ' + item.analysis + '</div><div class="section-seperator"></div>';
3933        }
3934        document.getElementById('profile-playerNews').innerHTML = html;
3935    }
3936}
3937function loadNews() {
3938    return new Promise(function (resolve, reject) {
3939        var url = 'https://drafthero.blob.core.windows.net/$web/${currentSeason}/playerNews/playerNews.json';
3940        getJSON(url, function (result) {
3941            playerNews = result.PlayerNews;
3942        });
3943    });
3944}
3945function getPlayerPointsBySource(playerID) {
3946    var html = '', blend = '', pts = 0;
3947    for (source in playersPointsBySource) {
3948        if (source === 'Custom') continue; // Skip Custom source for now
3949        pts = playersPointsBySource[source][playerID] ? playersPointsBySource[source][playerID].toFixed(0) : 'nr';
3950        blend = source !== 'Blended' ? ' (' + (blendObj[source].Blend * 100).toFixed(0) + '%)' : '';
3951        html += '<div><ul><li class="stat-label">' + source + '' + blend + '</li><li class="stat">' + pts + '</li></ul></div>';
3952    }
3953    return html;
3954}
3955function getPlayerSiteADP(playerID) {
3956    var adp = 0, html = '';
3957    var siteADP = playersADPBySite[playerID];
3958    for (site in siteADP) {
3959        adp = siteADP[site] !== 'NA' ? siteADP[site].toFixed(0) : 'NA';
3960        html += '<div><ul><li class="stat-label">' + site + '</li><li class="stat">' + adp + '</li></ul></div>';
3961    }
3962    return html;
3963}
3964function getPlayerCareerStats(playerID, pos) {
3965    var html = '', htmlHead = '', htmlBody = '';
3966    var _histStats = histStats;
3967
3968    try {
3969        //Fields:PaYd,PaTD,PaInt,RuYd,RuTD,Rec,ReYd,ReTD
3970        var stats = { 'Projected': null, 'Minus1': null, 'Minus2': null, 'Minus3': null };
3971        stats.Projected = playersStats[playerID];
3972        stats.Minus1 = _histStats.seasonStats[2021][playerID];
3973        stats.Minus2 = _histStats.seasonStats[2020][playerID];
3974        stats.Minus3 = _histStats.seasonStats[2019][playerID];
3975
3976        var posRank = { 'Projected': null, 'Minus1': null, 'Minus2': null, 'Minus3': null };
3977        posRank.Projected = playersPosRanking[playerID];
3978        posRank.Minus1 = _histStats.posRank[2021][playerID];
3979        posRank.Minus2 = _histStats.posRank[2020][playerID];
3980        posRank.Minus3 = _histStats.posRank[2019][playerID];
3981
3982        switch (pos) {
3983            case 'QB':
3984                htmlHead = '<thead><tr><th>Year</th><th>Pos Rank</th><th>PaYd</th><th>PaTD</th><th>PaInt</th><th>RuYd</th><th>RuTD</th></tr></thead>';
3985                if (stats.Projected) htmlBody = '<tr><td>2022</td><td>' + posRank.Projected + '</td><td>' + stats.Projected.PAYD + '</td><td>' + stats.Projected.PATD + '</td><td>' + stats.Projected.PAINT + '</td><td>' + stats.Projected.RUYD + '</td><td>' + stats.Projected.RUTD + '</td></tr>';
3986                if (stats.Minus1) htmlBody += '<tr><td>2021</td><td>' + posRank.Minus1 + '</td><td>' + stats.Minus1[0] + '</td><td>' + stats.Minus1[1] + '</td><td>' + stats.Minus1[2] + '</td><td>' + stats.Minus1[3] + '</td><td>' + stats.Minus1[4] + '</td></tr>';
3987                if (stats.Minus2) htmlBody += '<tr><td>2020</td><td>' + posRank.Minus2 + '</td><td>' + stats.Minus2[0] + '</td><td>' + stats.Minus2[1] + '</td><td>' + stats.Minus2[2] + '</td><td>' + stats.Minus2[3] + '</td><td>' + stats.Minus2[4] + '</td></tr>';
3988                if (stats.Minus3) htmlBody += '<tr><td>2019</td><td>' + posRank.Minus3 + '</td><td>' + stats.Minus3[0] + '</td><td>' + stats.Minus3[1] + '</td><td>' + stats.Minus3[2] + '</td><td>' + stats.Minus3[3] + '</td><td>' + stats.Minus3[4] + '</td></tr>';
3989                break;
3990            case 'RB':
3991                htmlHead = '<thead><tr><th>Year</th><th>Pos Rank</th><th>RuYd</th><th>RuTD</th><th>Recpt</th><th>ReYd</th><th>ReTD</th></tr></thead>';
3992                if (stats.Projected) htmlBody = '<tr><td>2022</td><td>' + posRank.Projected + '</td><td>' + stats.Projected.RUYD + '</td><td>' + stats.Projected.RUTD + '</td><td>' + stats.Projected.REC + '</td><td>' + stats.Projected.REYD + '</td><td>' + stats.Projected.RETD + '</td></tr>';
3993                if (stats.Minus1) htmlBody += '<tr><td>2021</td><td>' + posRank.Minus1 + '</td><td>' + stats.Minus1[3] + '</td><td>' + stats.Minus1[4] + '</td><td>' + stats.Minus1[5] + '</td><td>' + stats.Minus1[6] + '</td><td>' + stats.Minus1[7] + '</td></tr>';
3994                if (stats.Minus2) htmlBody += '<tr><td>2020</td><td>' + posRank.Minus2 + '</td><td>' + stats.Minus2[3] + '</td><td>' + stats.Minus2[4] + '</td><td>' + stats.Minus2[5] + '</td><td>' + stats.Minus2[6] + '</td><td>' + stats.Minus2[7] + '</td></tr>';
3995                if (stats.Minus3) htmlBody += '<tr><td>2019</td><td>' + posRank.Minus3 + '</td><td>' + stats.Minus3[3] + '</td><td>' + stats.Minus3[4] + '</td><td>' + stats.Minus3[5] + '</td><td>' + stats.Minus3[6] + '</td><td>' + stats.Minus3[7] + '</td></tr>';
3996                break;
3997            case 'WR':
3998            case 'TE':
3999                htmlHead = '<thead><tr><th>Year</th><th>Pos Rank</th><th>Recpt</th><th>ReYd</th><th>ReTD</th><th>RuYd</th><th>RuTD</th></tr></thead>';
4000                if (stats.Projected) htmlBody = '<tr><td>2022</td><td>' + posRank.Projected + '</td><td>' + stats.Projected.REC + '</td><td>' + stats.Projected.REYD + '</td><td>' + stats.Projected.RETD + '</td><td>' + (stats.Projected.RUYD || 0) + '</td><td>' + (stats.Projected.RUTD || 0) + '</td></tr>';
4001                if (stats.Minus1) htmlBody += '<tr><td>2021</td><td>' + posRank.Minus1 + '</td><td>' + stats.Minus1[5] + '</td><td>' + stats.Minus1[6] + '</td><td>' + stats.Minus1[7] + '</td><td>' + stats.Minus1[3] + '</td><td>' + stats.Minus1[4] + '</td></tr>';
4002                if (stats.Minus2) htmlBody += '<tr><td>2020</td><td>' + posRank.Minus2 + '</td><td>' + stats.Minus2[5] + '</td><td>' + stats.Minus2[6] + '</td><td>' + stats.Minus2[7] + '</td><td>' + stats.Minus2[3] + '</td><td>' + stats.Minus2[4] + '</td></tr>';
4003                if (stats.Minus3) htmlBody += '<tr><td>2019</td><td>' + posRank.Minus3 + '</td><td>' + stats.Minus3[5] + '</td><td>' + stats.Minus3[6] + '</td><td>' + stats.Minus3[7] + '</td><td>' + stats.Minus3[3] + '</td><td>' + stats.Minus3[4] + '</td></tr>';
4004                break;
4005        }
4006    } catch (e) {
4007        console.log('historical data not available');
4008    }
4009
4010    html = htmlHead + htmlBody;
4011    return html;
4012}
4013function getPlayerWeeklyRankings(playerID) {
4014    //Player ranking by position prior year
4015    var playerObj = playersObj[playerID];
4016    var pos = playerObj.Pos;
4017    var arrSummary = histStats.posRankingsSummary[playerID] || ['NA', 'NA', 'NA', 'NA', 'NA'];
4018    var htmlRankings = '', htmlLegend = '', percentage = null, opacity = 1;
4019    var arr = histStats.posRankingsWeekly[playerID];
4020
4021    if (arr !== undefined) {
4022        var obj = { "QB": 47, "RB": 104, "WR": 154, "TE": 72 };
4023        var objDepth = { "QB": 12, "RB": 24, "WR": 36, "TE": 12 };
4024
4025        htmlLegend = '<span><span class="label bg-' + pos + '">START: ' + arrSummary[1] + ' | STUD: ' + arrSummary[0] + '</span><span class="label bg-' + pos + ' opacity-60">BENCH: ' + arrSummary[2] + ' | BUST: ' + arrSummary[3] + '</span></span>';
4026        htmlRankings = '<div><div class="grid-bars">';
4027        for (var i = 0; i < arr.length; i++) {
4028            if (arr[i] !== undefined && arr[i] !== null && arr[i] !== 0) {
4029                percentage = (100 * (1 - arr[i] / obj[pos])).toFixed(1);
4030                opacity = arr[i] <= objDepth[pos] ? 1 : .6;
4031                htmlRankings += `<div><div style="width: ${percentage}%; height: ${percentage}%; opacity:${opacity}; background-color:#0e5b90cc">#${arr[i]}</div></div>`;
4032            } else {
4033                htmlRankings += `<div><div style="width: 0%; height: 0%">NA</div></div>`;
4034            }
4035        }
4036        htmlRankings += '</div>' + htmlLegend + '<span style="float:right" class="is-desc">Week 1-18 not including players bye week.</span></div>';
4037    }
4038    return htmlRankings;
4039}
4040function getValueStrArr(playerStatsObj, pos) {
4041    var strArr = ['NA', 'NA', 'NA', 0, 0, 0];
4042    if (playerStatsObj) {
4043        var maxPointsbyPosObj = { QB: [43, 534, 18], RB: [17, 2261, 102], WR: [16, 1711, 129], TE: [12, 1124, 90] };
4044        switch (pos) {
4045            case 'WR':
4046                strArr[0] = playerStatsObj.ReTD + ' + ' + (playerStatsObj.RuTD || 0) + ' = ' + playerStatsObj.TotalTD;
4047                strArr[1] = playerStatsObj.ReYd + ' + ' + (playerStatsObj.RuYd || 0) + ' = ' + playerStatsObj.TotalYd;
4048                strArr[2] = playerStatsObj.Recpt;
4049                strArr[3] = ~~(100 * playerStatsObj.TotalTD / maxPointsbyPosObj[pos][0]);
4050                strArr[4] = ~~(100 * playerStatsObj.TotalYd / maxPointsbyPosObj[pos][1]);
4051                strArr[5] = ~~(100 * playerStatsObj.Recpt / maxPointsbyPosObj[pos][2]);
4052                break;
4053            case 'RB':
4054                strArr[0] = playerStatsObj.RuTD + ' + ' + (playerStatsObj.ReTD || 0) + ' = ' + playerStatsObj.TotalTD;
4055                strArr[1] = playerStatsObj.RuYd + ' + ' + playerStatsObj.ReYd + ' = ' + playerStatsObj.TotalYd;
4056                strArr[2] = playerStatsObj.Recpt;
4057                strArr[3] = ~~(100 * playerStatsObj.TotalTD / maxPointsbyPosObj[pos][0]);
4058                strArr[4] = ~~(100 * playerStatsObj.TotalYd / maxPointsbyPosObj[pos][1]);
4059                strArr[5] = ~~(100 * playerStatsObj.Recpt / maxPointsbyPosObj[pos][2]);
4060                break;
4061            case 'QB':
4062                strArr[0] = playerStatsObj.PaTD + ' + ' + playerStatsObj.RuTD + ' = ' + playerStatsObj.TotalTD;
4063                strArr[1] = playerStatsObj.PaYd + ' + ' + playerStatsObj.RuYd + ' = ' + playerStatsObj.TotalYd;
4064                strArr[2] = playerStatsObj.PaInt;
4065                strArr[3] = ~~(100 * playerStatsObj.TotalTD / maxPointsbyPosObj[pos][0]);
4066                strArr[4] = ~~(100 * playerStatsObj.TotalYd / maxPointsbyPosObj[pos][1]);
4067                strArr[5] = ~~(100 * playerStatsObj.PaInt / maxPointsbyPosObj[pos][2]);
4068                break;
4069            case 'TE':
4070                strArr[0] = playerStatsObj.ReTD;
4071                strArr[1] = playerStatsObj.ReYd;
4072                strArr[2] = playerStatsObj.Recpt;
4073                strArr[3] = ~~(100 * playerStatsObj.TotalTD / maxPointsbyPosObj[pos][0]);
4074                strArr[4] = ~~(100 * playerStatsObj.TotalYd / maxPointsbyPosObj[pos][1]);
4075                strArr[5] = ~~(100 * playerStatsObj.Recpt / maxPointsbyPosObj[pos][2]);
4076                break;
4077        }
4078    }
4079    return strArr;
4080}
4081/*****************Notes**************************************************************
4082    Tab: Player Profile
4083************************************************************************************/
4084function showPlayer(playerID) {
4085    if (savedObj.statsLoaded === undefined) {
4086        loadPlayerStats().then(function (result) {
4087            if (result === 'success') {
4088                savedObj['statsLoaded'] = true;
4089                displayPlayer(playerID);
4090            }
4091        });
4092    } else {
4093        displayPlayer(playerID);
4094    }
4095}
4096function loadPlayerProfileHTML(playerID) {
4097    var url = '/draft/profile';
4098    getHTML(url, function (html) {
4099        savedObj.profileHTML = html;
4100        document.getElementById('player-panel').innerHTML = html;
4101        displayPlayer(playerID);
4102
4103        //Add controllers for tabs
4104        addEventListenerTabs(viewTab);
4105    });
4106}
4107function displayPlayer(playerID) {
4108    showPlayerProfile(playerID);
4109
4110    updatePlayerNews(playerID);
4111    updatePlayerNotes(playerID);
4112}
4113function showPlayerProfile(playerID) {
4114
4115    //Center: Player Rankings
4116    let playerObj = playersObj[playerID];
4117    let pos = playerObj.Pos;
4118    var depth = playersDepth[playerID] || '';
4119
4120    let summary = getPlayerSummary(playerID, playerObj);
4121    let sectionPoints = getPlayerPointsBySource(playerID);
4122    let sectionADP = getPlayerSiteADP(playerID);
4123    //let sectionCAREER = getPlayerCareerStats(playerID, pos);
4124    let sectionWEEKLY = getPlayerWeeklyRankings(playerID);
4125    let status = playersDrafted[playerID] !== undefined ? ' DRAFTED' : 'AVAILABLE';
4126    let buttons = '';
4127    if (window.location.pathname === '/draft') {
4128        status = getPlayerStatus(playerID);
4129        buttons = getButtons(playerID, status);
4130    }
4131
4132    document.getElementById('modal-profile').innerHTML = `
4133        <div class="modal-content sidepanel">
4134            <h2 id="profile-player" data-id="${playerID}" class="header">${playersNames[playerID]}, ${playerObj.Team} ${pos}${depth} 
4135            <span style="font-size:.65em; opacity: .8; margin-left: .5em"> ${status}<span>
4136            <button type="button" class="button-blue" style="margin-left:1em;" onclick="closeProfile()">CLOSE PROFILE</button>
4137            </h2>
4138            <div id="modal-info">
4139                <div class="section">
4140                    <div class="grid-row-autocol">${summary}</div>
4141                    <div class="" style="margin-top:.5em">${buttons}</div>
4142                </div>
4143                <div class="section"><h2>SEASON OUTLOOKS</h2>
4144                    <div id="profile-playerOutlook-RotoWire"></div>
4145                    <div id="profile-playerOutlook-4for4"></div>
4146                </div>
4147                <div class="section"><h2>FANTASY POINTS BY SOURCE</h2>
4148                    <div class="grid-row-autocol">${sectionPoints}</div>
4149                </div>
4150                <div class="section"><h2>ADP BY SITE</h2>
4151                    <div class="grid-row-autocol">${sectionADP}</div>
4152                </div>
4153                <div class="section"><h2>${pos} RANKINGS BY WEEK 2025</h2>
4154                    <div class="grid-row-autocol">${sectionWEEKLY}</div>
4155                </div>
4156                <div class="section"><h2>LATEST PLAYER NEWS</h2>
4157                    <div id="profile-playerNews"></div>
4158                </div>
4159            </div>
4160        </div>`;
4161    //<div class="section"><h2>RECENT CAREER STATS</h2>
4162    //    <div class="grid-row-autocol">${sectionCAREER}</div>
4163    //</div>
4164
4165    document.getElementById('modal-profile').style.display = "block";
4166}
4167function closeProfile() {
4168    document.getElementById('modal-profile').style.display = "none";
4169}
4170function getButtons(playerID, status) {
4171    let html = '';
4172
4173    //Player Status
4174    switch (status) {
4175        case 'AVAILABLE':
4176            html = `<button type="button" class="button" onclick="draftProfilePlayer()" style="width:62px; margin-right:60px">DRAFT</button>`;
4177            html += `<button type="button" class="button btn-avoid" onclick="avoidProfilePlayer()">AVOID</button><button type="button" class="button btn-watch" onclick="watchProfilePlayer()">WATCH</button><button type="button" class="button btn-target" onclick="targetProfilePlayer()">TARGET</button>`;
4178            break;
4179        case 'DRAFTED':
4180            html = `<button type="button" class="button is-disabled">TAKEN</button>`;
4181            break;
4182        case 'TARGET':
4183            html = `<button type="button" class="button" onclick="draftProfilePlayer()" style="width:62px; margin-right:60px">DRAFT</button>`;
4184            html += `<button type="button" class="button btn-target" onclick="clearTargetPlayer(${playerID})">REMOVE TARGET</button>`;
4185            break;
4186        case 'AVOID':
4187            html = `<button type="button" class="button" onclick="draftProfilePlayer()" style="width:62px; margin-right:60px">DRAFT</button>`;
4188            html += `<button type="button" class="button btn-avoid" onclick="avoidPlayer(${playerID})">CLEAR AVOID</button>`;
4189            break;
4190        case 'KEEPER':
4191            html = `<button type="button" class="button is-disabled"><strong>KEEPER</strong></button>`;
4192            break;
4193    }
4194    return html;
4195}
4196function getPlayerSummary(playerID, playerObj) {
4197    var points = 0, baseline = 0;
4198    var playerARV = playersARV[playerID];
4199    var pos = playerObj.Pos;
4200    var posRank = playersPosRanking[playerID] || 'NA';
4201    var arv = playerARV;
4202
4203    //player photo
4204    let html = getPhoto(playerID);
4205
4206    //Current year projected
4207    html += `<div><div>2026 Projected</div><div>PTS: <span class="stat">${playersPoints[playerID]}</span></div><div>ARV: <span class="stat">${arv}</span></div><div>Pos Rank: <span class="stat">${posRank}</span></div></div>`;
4208
4209    //Last year actual
4210    var strYear = '2025';
4211    if (histStats.seasonStats[strYear][playerID] !== undefined) {
4212        points = getPointsByPlayer(pos, histStats.seasonStats[strYear][playerID]);
4213        baseline = getPointsByPlayer(pos, histStats.baseline[strYear][pos]);
4214        playerARV = (points - baseline).toFixed(0);
4215        arv = playerARV > 0 ? '+' + playerARV : playerARV;
4216        html += `<div><div>${strYear} Actual</div><div>PTS: <span class="stat">${points.toFixed(0)}</span></div><div>ARV: <span class="stat">${arv}</span></div><div>Pos Rank: <span class="stat">${histStats.posRank[strYear][playerID]}</span></div></div>`;
4217    } else {
4218        html += `<div><div>${strYear} Actual</div><div>PTS: <span class="stat">NA</span></div><div>ARV: <span class="stat">NA</span></div><div>Pos Rank: <span class="stat">NA</span></div></div>`;
4219    }
4220
4221    //Prior year actual
4222    strYear = '2024';
4223    if (histStats.seasonStats[strYear][playerID] !== undefined) {
4224        points = getPointsByPlayer(pos, histStats.seasonStats[strYear][playerID]);
4225        baseline = getPointsByPlayer(pos, histStats.baseline[strYear][pos]);
4226        playerARV = (points - baseline).toFixed(0);
4227        arv = playerARV > 0 ? '+' + playerARV : playerARV;
4228        //html += '<div class="is-col"><fieldset><legend>2020 Actual</legend><div>PTS: <span class="stat">' + points + '</span></div><div>ARV: <span class="stat">' + arv + '</span></div><div>Pos Rank: <span class="stat">' + histStats.posRank[2020][playerID] + '</span></div></fieldset></div>';
4229        html += `<div><div>${strYear} Actual</div><div>PTS: <span class="stat">${points.toFixed(0)}</span></div><div>ARV: <span class="stat">${arv}</span></div><div>Pos Rank: <span class="stat">${histStats.posRank[strYear][playerID]}</span></div></div>`;
4230    } else {
4231        html += `<div><div>${strYear} Actual</div><div>PTS: <span class="stat">NA</span></div><div>ARV: <span class="stat">NA</span></div><div>Pos Rank: <span class="stat">NA</span></div></div>`;
4232    }
4233
4234    return html;
4235
4236}
4237function getPlayerStatus(playerID) {
4238    var playerStatus = 'AVAILABLE';
4239    switch (true) {
4240        case settings.Draft.Keepers && settings.Draft.Keepers[playerID] !== undefined:
4241            playerStatus = 'KEEPER';
4242            break;
4243        case playersDrafted[playerID] !== undefined:
4244            playerStatus = 'DRAFTED';
4245            break;
4246        case targetsObj[playerID] !== undefined:
4247            playerStatus = 'TARGET';
4248            break;
4249        case avoidsObj[playerID] !== undefined:
4250            playerStatus = 'AVOID';
4251            break;
4252        //Keepers?
4253    }
4254    return playerStatus;
4255}
4256//added
4257/*****************Notes**************************************************************
4258    Player Profile Functions
4259************************************************************************************/
4260function draftProfilePlayer() {
4261    var playerID = ~~document.getElementById('profile-player').getAttribute('data-id');
4262    if (playerID > 0 && playersDrafted[playerID] === undefined) draft(playerID);
4263    closeProfile();
4264}
4265function avoidProfilePlayer() {
4266    var playerID = ~~document.getElementById('profile-player').getAttribute('data-id');
4267    if (playerID > 0 && playersDrafted[playerID] === undefined) avoidPlayer(playerID, true);
4268    closeProfile();
4269}
4270function watchProfilePlayer() {
4271    var playerID = ~~document.getElementById('profile-player').getAttribute('data-id');
4272    if (playerID > 0) watchPlayer(playerID, true);
4273    closeProfile();
4274}
4275function targetProfilePlayer() {
4276    var playerID = ~~document.getElementById('profile-player').getAttribute('data-id');
4277    var name = playersObj[playerID].NameObj.Full;
4278    var adp = playersADP[playerID] || 'Undrafted';
4279    var header = ' Target ' + name + ' (ADP #' + adp + ')';
4280    var pick = getTargetPick(playerID);    
4281    var str = 'Unable to target ' + name + '. He is expected to be drafted before your first/next pick or is expected to go undrafted.';
4282    var nextPick = getTargetPickNext(playerID);
4283
4284    closeProfile();
4285
4286    switch (true) {
4287        case (pick === 0):
4288            openModal(str, header, null, null);
4289            break;
4290
4291        case (pick > 0 && nextPick > 0 && nextPick - adp < 12):
4292            const dif = nextPick - adp;
4293            str = name + ' is expected to be drafted #' + adp + ' overall in round ' + draftPicksObj[adp].rnd + '. Based on his ADP, you should target him with your pick #' + pick + ' in round ' + myDraftPicksObj[pick].rnd + '.  If you feel he may fall at least ' + dif + ' picks, you can target him with your pick #' + nextPick + ' in round ' + myDraftPicksObj[nextPick].rnd + '.';
4294            openModalTarget(str, header, 'Target with Pick #' + pick, function () {
4295                targetPlayer(playerID, pick);
4296            }, 'Target with Pick #' + nextPick, function () {
4297                targetPlayer(playerID, nextPick);
4298            });
4299            break;
4300
4301        case (pick > 0):
4302            str = name + ' is expected to be drafted in round ' + draftPicksObj[adp].rnd + ' (#' + adp + ' overall). Would you like to target him with your round ' + myDraftPicksObj[pick].rnd + ' pick (#' + pick + ' overall)?';
4303            openModal(str, header, 'Target Player', function () {
4304                targetPlayer(playerID, pick);
4305            });
4306            break;
4307    }
4308
4309}
4310function removeProfilePlayer() {
4311    var playerID = ~~document.getElementById('profile-player').getAttribute('data-id');
4312    if (playerID > 0 && playersDrafted[playerID] === undefined) avoid(playerID);
4313    closeProfile();
4314}
4315function getTargetPick(playerID) {
4316    var pick = 0;
4317    if (playerID > 0 && playersDrafted[playerID] === undefined) {
4318        var adp = playersADP[playerID];
4319        //find pick to draft
4320        if (adp !== undefined) {
4321            for (myDraftPick in myDraftPicksObj) {
4322                if (myDraftPicksObj[myDraftPick].type === 'Optimal' && myDraftPick <= adp && adp >= draftPick) {
4323                    pick = ~~myDraftPick;
4324                }
4325            }
4326        }
4327    }
4328    return pick;
4329}
4330function getTargetPickNext(playerID) {
4331    var pick = 0;
4332    var count = 0, myPickNumber = 0;
4333    if (playerID > 0 && playersDrafted[playerID] === undefined) {
4334        var adp = playersADP[playerID];
4335        //find pick to draft
4336        if (adp !== undefined) {
4337            for (myDraftPick in myDraftPicksObj) {
4338                if (myDraftPicksObj[myDraftPick].type === 'Optimal' && myDraftPick <= adp && adp >= draftPick) {
4339                    pick = ~~myDraftPick;
4340                    myPickNumber = count;
4341                }
4342                count++;
4343            }
4344        }
4345    }
4346    if (Object.keys(myDraftPicksObj)[myPickNumber + 1] && myDraftPicksObj[Object.keys(myDraftPicksObj)[myPickNumber + 1]].type === 'Optimal') {
4347        return Number(Object.keys(myDraftPicksObj)[myPickNumber + 1]);
4348    }
4349    return null;
4350}
4351
4352//Connect to Draft Hero Extension
4353var extensionId = 'bedajlgghndfjocdbmhmkcdlpljaakad';//production
4354if (window.location.origin === 'http://localhost:1337') {
4355    //extensionId = 'pcedekkcgnlfeeleaaiplkpfgkclkkli';//development
4356    //extensionId = 'dmbcaebgofnflkolheinkgacgnckcbng';//work
4357    extensionId = 'lochkkclbeaaebfmckjmhekmjkdgbgll';//new work
4358}
4359
4360//call background.js from app
4361function callBackgroundScript(message, callback) {
4362    chrome.runtime.sendMessage(extensionId, message,
4363        function (response) {
4364            callback(response);
4365        }
4366    );
4367}
4368function testConnection() {
4369    //updateSyncStatus('test extension connection.');
4370    return new Promise(function (resolve, reject) {
4371        callBackgroundScript({ type: 'test' }, function (response) {
4372            if (response && response.success) {
4373                resolve('success');
4374            } else {
4375                resolve('notfound');
4376            }
4377        });
4378    });
4379}
4380
4381//listen to messages from extension
4382window.addEventListener("message", (event) => {
4383    // We only accept messages from ourselves
4384    if (event.source !== window) return;
4385    if (event.data && (event.data.from === "FROM_EXTENSION")) {
4386        switch (event.data.type) {
4387            case 'imported':
4388                mapLeague();
4389                break;
4390            case 'draft':
4391                importDraftInfo = getLocal('importDraftInfo');
4392                if (typeof mapDraft === 'function') {
4393                    mapDraft(importDraftInfo.Site, importDraftInfo.SiteID);
4394                } else {
4395                    console.log("mapDraft is not defined or not a function.");
4396                }                
4397                break;
4398            case 'rosters':
4399                mapRosters(importRosterInfo.Site, importRosterInfo.SiteID);
4400                break;
4401            case 'draftorder':
4402                mapDraftOrder();
4403                break;
4404        }
4405    }
4406}, false);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.