diff --git a/package.nw/lib/roster.js b/package.nw/lib/roster.js
index d015832e..d7997f66 100644
--- a/package.nw/lib/roster.js
+++ b/package.nw/lib/roster.js
@@ -465,1815 +465,11 @@ function processRoster(roster)
 
 function viewRoster()
 {
-  var bands = Object();
-  var modes = Object();
-
-  // can the following block where we figure out how the roster is being used
-  // be a function itself that we can call in at the start of this?
-  var callMode = g_rosterSettings.callsign;
-  var onlyHits = false;
-  var isAwardTracker = false;
-
-  if (callMode == "hits")
-  {
-    callMode = "all";
-    onlyHits = true;
-  }
-  if (referenceNeed.value == LOGBOOK_AWARD_TRACKER)
-  {
-    callMode = "all";
-    onlyHits = false;
-    isAwardTracker = true;
-    g_rosterSettings.huntNeed = "confirmed";
-  }
-  // this appears to be determine if we should show the OAMS column
-  // if the user is not in offline mode and has OAMS enabled, this could
-  // be it's own function maybe?
-  var canMsg =
-    window.opener.g_mapSettings.offlineMode == false &&
-    window.opener.g_appSettings.gtShareEnable == "true" &&
-    window.opener.g_appSettings.gtMsgEnable == "true";
-
-  // The following 3 sections deal with QSLing, do we break them out
-  // individually or lump them into a qslUser function that sets
-  // all three at the same time?
-  // this section is for LoTW users, can be a function
-  if (window.opener.g_callsignLookups.lotwUseEnable == true)
-  {
-    usesLoTWDiv.style.display = "";
-    if (g_rosterSettings.usesLoTW == true)
-    {
-      maxLoTW.style.display = "";
-      maxLoTWView.style.display = "";
-    }
-    else
-    {
-      maxLoTW.style.display = "none";
-      maxLoTWView.style.display = "none";
-    }
-  }
-  else
-  {
-    usesLoTWDiv.style.display = "none";
-    maxLoTW.style.display = "none";
-    maxLoTWView.style.display = "none";
-  }
-
-  // eQSL - function
-  if (window.opener.g_callsignLookups.eqslUseEnable == true) useseQSLDiv.style.display = "";
-  else useseQSLDiv.style.display = "none";
-
-  // OQRS - function
-  if (window.opener.g_callsignLookups.oqrsUseEnable == true) usesOQRSDiv.style.display = "";
-  else usesOQRSDiv.style.display = "none";
-
-  // dealing with spots
-  if (g_rosterSettings.columns.Spot == true) onlySpotDiv.style.display = "";
-  else onlySpotDiv.style.display = "none";
-
-  // callmode (all or only new)
-  if (callMode == "all") allOnlyNewDiv.style.display = "";
-  else allOnlyNewDiv.style.display = "none";
-
-  // hunting mode - can this be comibined with the roster mode stuff on
-  // on line 441 above in that function or be it's own?
-  var huntIndex, workedIndex, layeredMode;
-  if (g_rosterSettings.huntNeed == "mixed")
-  {
-    huntIndex = g_confirmed;
-    workedIndex = g_worked;
-    layeredMode = LAYERED_MODE_FOR[String(g_rosterSettings.reference)];
-  }
-  else if (g_rosterSettings.huntNeed == "worked")
-  {
-    huntIndex = g_worked;
-    workedIndex = false;
-    layeredMode = false;
-  }
-  else if (g_rosterSettings.huntNeed == "confirmed")
-  {
-    huntIndex = g_confirmed;
-    workedIndex = g_worked;
-    layeredMode = false;
-  }
-  else
-  {
-    huntIndex = false;
-    workedIndex = false;
-    layeredMode = false;
-  }
-
-  var now = timeNowSec();
-
-  // First loop, exclude calls, mostly based on "Exceptions" settings
-  // this whole section is full of individual if's that could be broken out
-  for (var callHash in callRoster)
-  {
-    var entry = callRoster[callHash];
-    var callObj = entry.callObj;
-
-    var call = entry.DEcall;
-
-    entry.tx = true;
-    callObj.shouldAlert = false;
-    callObj.reason = Array();
-    callObj.awardReason = "Callsign";
-
-    if (now - callObj.age > window.opener.g_mapSettings.rosterTime)
-    {
-      entry.tx = false;
-      entry.alerted = false;
-      callObj.qrz = false;
-      callObj.reset = true;
-      continue;
-    }
-    if (window.opener.g_instances[callObj.instance].crEnable == false)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (call in g_blockedCalls)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (
-      entry.DXcall + " from All" in g_blockedCQ ||
-      entry.DXcall + " from " + window.opener.g_dxccToAltName[callObj.dxcc] in g_blockedCQ
-    )
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (callObj.dxcc in g_blockedDxcc)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (g_rosterSettings.cqOnly == true && callObj.CQ == false)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (g_rosterSettings.useRegex && g_rosterSettings.callsignRegex.length > 0)
-    {
-      try
-      {
-        if (!call.match(g_rosterSettings.callsignRegex))
-        {
-          entry.tx = false;
-          continue;
-        }
-      }
-      catch (e) {}
-    }
-    if (g_rosterSettings.requireGrid == true && callObj.grid.length != 4)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (g_rosterSettings.wantMinDB == true && entry.message.SR < g_rosterSettings.minDb)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (g_rosterSettings.wantMaxDT == true && Math.abs(entry.message.DT) > g_rosterSettings.maxDT)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (g_rosterSettings.wantMinFreq == true && entry.message.DF < g_rosterSettings.minFreq)
-    {
-      entry.tx = false;
-      continue;
-    }
-    if (g_rosterSettings.wantMaxFreq == true && entry.message.DF > g_rosterSettings.maxFreq)
-    {
-      entry.tx = false;
-      continue;
-    }
-
-    if (g_rosterSettings.noMsg == true)
-    {
-      try
-      {
-        if (callObj.msg.match(g_rosterSettings.noMsgValue))
-        {
-          entry.tx = false;
-          continue;
-        }
-      }
-      catch (e) {}
-    }
-    if (g_rosterSettings.onlyMsg == true)
-    {
-      try
-      {
-        if (!callObj.msg.match(g_rosterSettings.onlyMsgValue))
-        {
-          entry.tx = false;
-          continue;
-        }
-      }
-      catch (e) {}
-    }
-
-    if (callObj.dxcc == window.opener.g_myDXCC)
-    {
-      if (g_rosterSettings.noMyDxcc == true)
-      {
-        entry.tx = false;
-        continue;
-      }
-    }
-    else
-    {
-      if (g_rosterSettings.onlyMyDxcc == true)
-      {
-        entry.tx = false;
-        continue;
-      }
-    }
-
-    if (window.opener.g_callsignLookups.lotwUseEnable == true && g_rosterSettings.usesLoTW == true)
-    {
-      if (!(call in window.opener.g_lotwCallsigns))
-      {
-        entry.tx = false;
-        continue;
-      }
-      if (g_rosterSettings.maxLoTW < 27)
-      {
-        var months = (g_day - window.opener.g_lotwCallsigns[call]) / 30;
-        if (months > g_rosterSettings.maxLoTW)
-        {
-          entry.tx = false;
-          continue;
-        }
-      }
-    }
-
-    if (window.opener.g_callsignLookups.eqslUseEnable == true && g_rosterSettings.useseQSL == true)
-    {
-      if (!(call in window.opener.g_eqslCallsigns))
-      {
-        entry.tx = false;
-        continue;
-      }
-    }
-
-    if (window.opener.g_callsignLookups.oqrsUseEnable == true && g_rosterSettings.usesOQRS == true)
-    {
-      if (!(call in window.opener.g_oqrsCallsigns))
-      {
-        entry.tx = false;
-        continue;
-      }
-    }
-
-    if (callMode != "all")
-    {
-      if (entry.DXcall == "CQ DX" && callObj.dxcc == window.opener.g_myDXCC)
-      {
-        entry.tx = false;
-        continue;
-      }
-
-      var hash = hashMaker(call, callObj, g_rosterSettings.reference);
-      if (callMode == "worked" && hash in g_worked.call)
-      {
-        entry.tx = false;
-        continue;
-      }
-      if (callMode == "confirmed" && hash in g_confirmed.call)
-      {
-        entry.tx = false;
-        continue;
-      }
-
-      if (g_rosterSettings.hunting == "grid")
-      {
-        var hash = hashMaker(callObj.grid.substr(0, 4),
-          callObj, g_rosterSettings.reference);
-        if (huntIndex && hash in huntIndex.grid)
-        {
-          entry.tx = false;
-          continue;
-        }
-        if (callObj.grid.length == 0)
-        {
-          entry.tx = false;
-          continue;
-        }
-        continue;
-      }
-      if (g_rosterSettings.hunting == "dxcc")
-      {
-        var hash = hashMaker(String(callObj.dxcc),
-          callObj, g_rosterSettings.reference);
-
-        if (huntIndex && (hash in huntIndex.dxcc))
-        {
-          entry.tx = false;
-          continue;
-        }
-        continue;
-      }
-
-      if (g_rosterSettings.hunting == "dxccs" && r_currentDXCCs != -1)
-      {
-        if (callObj.dxcc != r_currentDXCCs)
-        {
-          entry.tx = false;
-          continue;
-        }
-      }
-
-      if (g_rosterSettings.hunting == "wpx")
-      {
-        if (String(callObj.px) == null)
-        {
-          entry.tx = false;
-          continue;
-        }
-        var hash = hashMaker(String(callObj.px),
-          callObj, g_rosterSettings.reference);
-
-        if (huntIndex && (hash in huntIndex.px))
-        {
-          entry.tx = false;
-          continue;
-        }
-
-        continue;
-      }
-
-      if (g_rosterSettings.hunting == "cq")
-      {
-        var huntTotal = callObj.cqza.length;
-        if (huntTotal == 0 || !huntIndex)
-        {
-          entry.tx = false;
-          continue;
-        }
-        var huntFound = 0;
-        for (index in callObj.cqza)
-        {
-          var hash = hashMaker(callObj.cqza[index], callObj, g_rosterSettings.reference);
-
-          if (hash in huntIndex.cqz) huntFound++;
-        }
-        if (huntFound == huntTotal)
-        {
-          entry.tx = false;
-          continue;
-        }
-        continue;
-      }
-
-      if (g_rosterSettings.hunting == "itu")
-      {
-        var huntTotal = callObj.ituza.length;
-        if (huntTotal == 0 || !huntIndex)
-        {
-          entry.tx = false;
-          continue;
-        }
-        var huntFound = 0;
-        for (index in callObj.ituza)
-        {
-          var hash = hashMaker(callObj.ituza[index], callObj, g_rosterSettings.reference);
-
-          if (hash in huntIndex.ituz) huntFound++;
-        }
-        if (huntFound == huntTotal)
-        {
-          entry.tx = false;
-          continue;
-        }
-
-        if (callObj.grid.length == 0)
-        {
-          entry.tx = false;
-          continue;
-        }
-        continue;
-      }
-
-      if (g_rosterSettings.hunting == "usstates" && window.opener.g_callsignLookups.ulsUseEnable == true)
-      {
-        var state = callObj.state;
-        var finalDxcc = callObj.dxcc;
-        if (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6)
-        {
-          if (state in window.opener.g_StateData)
-          {
-            var hash = hashMaker(state, callObj, g_rosterSettings.reference);
-
-            if (huntIndex && hash in huntIndex.state)
-            {
-              entry.tx = false;
-              continue;
-            }
-          }
-          else entry.tx = false;
-        }
-        else entry.tx = false;
-
-        continue;
-      }
-
-      if (g_rosterSettings.hunting == "usstate" && g_currentUSCallsigns)
-      {
-        if (call in g_currentUSCallsigns)
-        {
-          // Do Nothing
-        }
-        else
-        {
-          entry.tx = false;
-          continue;
-        }
-        continue;
-      }
-    }
-    if (isAwardTracker)
-    {
-      var tx = false;
-      var baseHash = hashMaker("", callObj, g_rosterSettings.reference);
-
-      for (var award in g_awardTracker)
-      {
-        if (g_awardTracker[award].enable)
-        {
-          tx = testAward(award, callObj, baseHash);
-          if (tx)
-          {
-            var x = g_awardTracker[award];
-            callObj.awardReason =
-              g_awards[x.sponsor].awards[x.name].tooltip +
-              " (" +
-              g_awards[x.sponsor].sponsor +
-              ")";
-
-            break;
-          }
-        }
-      }
-      entry.tx = tx;
-    }
-  }
-
-  // these vars, do they rely on anything between the top and here?
-  // if not could they be put in the var list at the beginning?
-  var hasGtPin = false;
-
-  var newCallList = Array();
-  var inversionAlpha = "DD";
-  var row = "#000000";
-  var bold = "#000000;font-weight: bold;";
-  var unconf = "background-clip:padding-box;box-shadow: 0 0 7px 3px inset ";
-  var layeredAlpha = "77";
-  var layeredInversionAlpha = "66";
-  var layeredUnconf = "background-clip:padding-box;box-shadow: 0 0 4px 2px inset ";
-  var layeredUnconfAlpha = "AA";
-
-  // Second loop, hunting and highlighting
-  for (var callHash in callRoster)
-  {
-    var entry = callRoster[callHash];
-    var callObj = entry.callObj;
-
-    // Special case check for called station
-    if (callObj.qrz == true && entry.tx == false)
-    {
-      // The instance has to be enabled
-      if (window.opener.g_instances[callObj.instance].crEnable == true)
-      {
-        // Calling us, but we wouldn't normally display
-        // If they are not ignored or we're in a QSO with them, var it through
-
-        if ((!(entry.DEcall in g_blockedCalls) && !(callObj.dxcc in g_blockedDxcc)) ||
-          window.opener.g_instances[callObj.instance].status.DXcall == entry.DEcall)
-        {
-          entry.tx = true;
-        }
-      }
-    }
-
-    // Only render entries with `tx == true`, ignore the rest
-    if (callObj.dxcc != -1 && entry.tx == true)
-    {
-      // In layered mode ("Hunting: mixed") the workHashSuffix becomes a more stricter 'live band',
-      // while the layered suffix is a broader 'mixed band'
-      var workHashSuffix, layeredHashSuffix;
-      if (layeredMode)
-      {
-        workHashSuffix = hashMaker("", callObj, layeredMode);
-        layeredHashSuffix = hashMaker("", callObj, g_rosterSettings.reference);
-      }
-      else
-      {
-        workHashSuffix = hashMaker("", callObj, g_rosterSettings.reference);
-        layeredHashSuffix = false
-      }
-      var workHash = workHashSuffix; // TODO: Remove after replacing all occurrences with Suffix
-
-      var callsign = entry.DEcall;
-
-      callObj.hunting = {}
-      callObj.callFlags = {}
-
-      var colorObject = Object();
-
-      var callPointer = callObj.CQ == true ? "cursor:pointer" : "";
-
-      var didWork = false;
-
-      var call = "#FFFF00";
-      var grid = "#00FFFF";
-      var calling = "#90EE90";
-      var dxcc = "#FFA500";
-      var state = "#90EE90";
-      var cnty = "#CCDD00";
-      var cont = "#00DDDD";
-      var cqz = "#DDDDDD";
-      var ituz = "#DDDDDD";
-      var wpx = "#FFFF00";
-
-      hasGtPin = false;
-      var shouldAlert = false;
-      var callBg, gridBg, callingBg, dxccBg, stateBg, cntyBg, contBg, cqzBg, ituzBg, wpxBg, gtBg;
-      var callConf, gridConf, callingConf, dxccConf, stateConf, cntyConf, contConf, cqzConf, ituzConf, wpxConf;
-
-      callBg = gridBg = callingBg = dxccBg = stateBg = cntyBg = contBg = cqzBg = ituzBg = wpxBg = gtBg = row;
-
-      callConf = gridConf = callingConf = dxccConf = stateConf = cntyConf = contConf = cqzConf = ituzConf = wpxConf =
-        "";
-
-      var hash = callsign + workHashSuffix;
-      var layeredHash = layeredHashSuffix && (callsign + layeredHashSuffix)
-
-      // Call worked in current logbook settings, regardless of hunting mode
-      if (hash in g_worked.call)
-      {
-        callObj.callFlags.worked = true;
-        didWork = true;
-        callConf = `${unconf}${call}${inversionAlpha};`;
-
-        if (hash in g_confirmed.call)
-        {
-          callObj.callFlags.confirmed = true;
-          callPointer = "text-decoration: line-through; ";
-          callConf = "";
-        }
-      }
-
-      // Calls that have OAMS chat support
-      if (
-        callsign in window.opener.g_gtCallsigns &&
-        window.opener.g_gtCallsigns[callsign] in window.opener.g_gtFlagPins &&
-        window.opener.g_gtFlagPins[window.opener.g_gtCallsigns[callsign]].canmsg == true
-      )
-      {
-        callObj.callFlags.oams = true;
-        // grab the CID
-        colorObject.gt = window.opener.g_gtCallsigns[callsign];
-        hasGtPin = true;
-      }
-      else
-      {
-        colorObject.gt = 0;
-      }
-
-      // We only do hunt highlighting when showing all entries
-      // This means "Callsigns: All Traffic", "Callsigns: All Traffic/Only Wanted" and "Logbook: Award Tracker"
-      // There is no highlighting in other modes
-      if (callMode == "all")
-      {
-        // Skip when "only new calls"
-        // Questions: Move to the first loop? Why only skip new calls in "all traffic" and not other modes?
-        if (allOnlyNew.checked == true && didWork && callObj.qrz == false)
-        {
-          entry.tx = false;
-          continue;
-        }
-
-        // Hunting for callsigns
-        if (huntCallsign.checked == true)
-        {
-          var hash = callsign + workHashSuffix;
-          var layeredHash = layeredMode && (callsign + layeredHashSuffix)
-
-          if (huntIndex && !(hash in huntIndex.call))
-          {
-            shouldAlert = true;
-
-            callObj.reason.push("call");
-
-            if (workedIndex && hash in workedIndex.call)
-            {
-              if (layeredMode && layeredHash in huntIndex.call)
-              {
-                callObj.hunting.call = "worked-and-mixed";
-                callConf = `${layeredUnconf}${call}${layeredUnconfAlpha};`;
-                callBg = `${call}${layeredInversionAlpha}`;
-                call = bold;
-              }
-              // /* Currently we don't have a way to figure out
-              //  * if the call is worked only in this band or also others,
-              //  * so we cannot cover this particular combination
-              //  * and have to default to just showing it as plain "worked"
-              //  */
-              // else if (layeredMode && layeredHash in workedIndex.call)
-              // {
-              //   callObj.hunting.call = "worked-and-mixed-worked";
-              //   callConf = `${layeredUnconf}${call}${layeredAlpha};`;
-              // }
-              else
-              {
-                callObj.hunting.call = "worked";
-                callConf = `${unconf}${call}${inversionAlpha};`;
-              }
-            }
-            else
-            {
-              if (layeredMode && layeredHash in huntIndex.call)
-              {
-                callObj.hunting.call = "mixed";
-                callBg = `${call}${layeredAlpha};`;
-                call = bold;
-              }
-              else if (layeredMode && layeredHash in workedIndex.call)
-              {
-                callObj.hunting.call = "mixed-worked";
-                callConf = `${unconf}${call}${layeredAlpha};`;
-              }
-              else
-              {
-                callObj.hunting.call = "hunted";
-                callBg = `${call}${inversionAlpha};`;
-                call = bold;
-              }
-            }
-          }
-        }
-
-        // Hunting for "stations calling you"
-        if (huntQRZ.checked == true && callObj.qrz == true)
-        {
-          callObj.callFlags.calling = true
-          shouldAlert = true;
-          callObj.reason.push("qrz");
-        }
-
-        // Hunting for stations with OAMS
-        if (huntOAMS.checked == true && hasGtPin == true)
-        {
-          callObj.hunting.oams = "hunted";
-          shouldAlert = true;
-          callObj.reason.push("oams");
-        }
-
-        // Hunting for grids
-        if (huntGrid.checked == true && callObj.grid.length > 1)
-        {
-          var hash = callObj.grid.substr(0, 4) + workHashSuffix;
-          var layeredHash = layeredMode && (callObj.grid.substr(0, 4) + layeredHashSuffix)
-
-          if (huntIndex && !(hash in huntIndex.grid))
-          {
-            shouldAlert = true;
-
-            callObj.reason.push("grid");
-
-            if (workedIndex && hash in workedIndex.grid)
-            {
-              if (layeredMode && layeredHash in huntIndex.grid)
-              {
-                callObj.hunting.grid = "worked-and-mixed";
-                gridConf = `${layeredUnconf}${grid}${layeredUnconfAlpha};`;
-                gridBg = `${grid}${layeredInversionAlpha}`;
-                grid = bold;
-              }
-              else
-              {
-                callObj.hunting.grid = "worked";
-                gridConf = `${unconf}${grid}${inversionAlpha};`;
-              }
-            }
-            else
-            {
-              if (layeredMode && layeredHash in huntIndex.grid)
-              {
-                callObj.hunting.grid = "mixed";
-                gridBg = `${grid}${layeredAlpha};`;
-                grid = bold;
-              }
-              else if (layeredMode && layeredHash in workedIndex.grid)
-              {
-                callObj.hunting.grid = "mixed-worked";
-                gridConf = `${unconf}${grid}${layeredAlpha};`;
-              }
-              else
-              {
-                callObj.hunting.grid = "hunted";
-                gridBg = `${grid}${inversionAlpha};`;
-                grid = bold;
-              }
-            }
-          }
-        }
-
-        // Hunting for DXCC
-        if (huntDXCC.checked == true)
-        {
-          var hash = String(callObj.dxcc) + workHashSuffix;
-          var layeredHash = layeredMode && (String(callObj.dxcc) + layeredHashSuffix)
-
-          if (huntIndex && !(hash in huntIndex.dxcc))
-          {
-            shouldAlert = true;
-
-            callObj.reason.push("dxcc");
-
-            if (workedIndex && hash in workedIndex.dxcc)
-            {
-              if (layeredMode && layeredHash in huntIndex.dxcc)
-              {
-                callObj.hunting.dxcc = "worked-and-mixed";
-                dxccConf = `${layeredUnconf}${dxcc}${layeredUnconfAlpha};`;
-                dxccBg = `${dxcc}${layeredInversionAlpha}`;
-                dxcc = bold;
-              }
-              else
-              {
-                callObj.hunting.dxcc = "worked";
-                dxccConf = `${unconf}${dxcc}${inversionAlpha};`;
-              }
-            }
-            else
-            {
-              if (layeredMode && layeredHash in huntIndex.dxcc)
-              {
-                callObj.hunting.dxcc = "mixed";
-                dxccBg = `${dxcc}${layeredAlpha};`;
-                dxcc = bold;
-              }
-              else if (layeredMode && layeredHash in workedIndex.dxcc)
-              {
-                callObj.hunting.dxcc = "mixed-worked";
-                dxccConf = `${unconf}${dxcc}${layeredAlpha};`;
-              }
-              else
-              {
-                callObj.hunting.dxcc = "hunted";
-                dxccBg = `${dxcc}${inversionAlpha};`;
-                dxcc = bold;
-              }
-            }
-          }
-        }
-
-        // Hunting for US States
-        if (huntState.checked == true && window.opener.g_callsignLookups.ulsUseEnable == true)
-        {
-          var stateSearch = callObj.state;
-          var finalDxcc = callObj.dxcc;
-          if (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6)
-          {
-            if (stateSearch in window.opener.g_StateData)
-            {
-              var hash = stateSearch + workHashSuffix;
-              var layeredHash = layeredMode && (stateSearch + layeredHashSuffix)
-
-              if (huntIndex && !(hash in huntIndex.state))
-              {
-                shouldAlert = true;
-
-                callObj.reason.push("state");
-
-                if (workedIndex && hash in workedIndex.state)
-                {
-                  if (layeredMode && layeredHash in huntIndex.state)
-                  {
-                    callObj.hunting.state = "worked-and-mixed";
-                    stateConf = `${layeredUnconf}${state}${layeredUnconfAlpha};`;
-                    stateBg = `${state}${layeredInversionAlpha}`;
-                    state = bold;
-                  }
-                  else
-                  {
-                    callObj.hunting.state = "worked";
-                    stateConf = `${unconf}${state}${inversionAlpha};`;
-                  }
-                }
-                else
-                {
-                  if (layeredMode && layeredHash in huntIndex.state)
-                  {
-                    callObj.hunting.state = "mixed";
-                    stateBg = `${state}${layeredAlpha};`;
-                    state = bold;
-                  }
-                  else if (layeredMode && layeredHash in workedIndex.state)
-                  {
-                    callObj.hunting.state = "mixed-worked";
-                    stateConf = `${unconf}${state}${layeredAlpha};`;
-                  }
-                  else
-                  {
-                    callObj.hunting.state = "hunted";
-                    stateBg = `${state}${inversionAlpha};`;
-                    state = bold;
-                  }
-                }
-              }
-            }
-          }
-        }
-
-        // Hunting for US Counties
-        if (huntCounty.checked == true && window.opener.g_callsignLookups.ulsUseEnable == true)
-        {
-          var finalDxcc = callObj.dxcc;
-          if (
-            callObj.cnty &&
-            (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6 || finalDxcc == 202) &&
-            callObj.cnty.length > 0
-          )
-          {
-            var hash = callObj.cnty + (layeredMode ? layeredHashSuffix : workHashSuffix);
-
-            if ((huntIndex && !(hash in huntIndex.cnty)) || callObj.qual == false)
-            {
-              if (callObj.qual == false)
-              {
-                var counties = window.opener.g_zipToCounty[callObj.zipcode];
-                var foundHit = false;
-                for (var cnt in counties)
-                {
-                  var hh = counties[cnt] + workHash;
-                  callObj.cnty = counties[cnt];
-                  if (huntIndex && !(hh in huntIndex.cnty))
-                  {
-                    foundHit = true;
-                    break;
-                  }
-                }
-                if (foundHit) shouldAlert = true;
-              }
-              else
-              {
-                shouldAlert = true;
-              }
-
-              if (shouldAlert)
-              {
-                callObj.reason.push("cnty");
-
-                if (workedIndex && hash in workedIndex.cnty)
-                {
-                  callObj.hunting.cnty = "worked";
-                  cntyConf = `${unconf}${cnty}${inversionAlpha};`;
-                }
-                else
-                {
-                  callObj.hunting.cnty = "hunted";
-                  cntyBg = `${cnty}${inversionAlpha}`;
-                  cnty = bold;
-                }
-              }
-            }
-          }
-        }
-
-        // Hunting for CQ Zones
-        if (huntCQz.checked == true)
-        {
-          var huntTotal = callObj.cqza.length;
-          var huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
-
-          for (index in callObj.cqza)
-          {
-            var hash = callObj.cqza[index] + workHashSuffix;
-            var layeredHash = layeredMode && (callObj.cqza[index] + layeredHashSuffix)
-
-            if (huntIndex && hash in huntIndex.cqz) huntFound++;
-            if (layeredMode && layeredHash in huntIndex.cqz) layeredFound++;
-            if (workedIndex && hash in workedIndex.cqz) workedFound++;
-            if (layeredMode && layeredHash in workedIndex.cqz) layeredWorkedFound++;
-          }
-          if (huntFound != huntTotal)
-          {
-            shouldAlert = true;
-            callObj.reason.push("cqz");
-
-            if (workedIndex && workedFound == huntTotal)
-            {
-              if (layeredMode && layeredFound == huntTotal)
-              {
-                callObj.hunting.cqz = "worked-and-mixed";
-                cqzConf = `${layeredUnconf}${cqz}${layeredUnconfAlpha};`;
-                cqzBg = `${cqz}${layeredInversionAlpha}`;
-                cqz = bold;
-              }
-              else
-              {
-                callObj.hunting.cqz = "worked";
-                cqzConf = `${unconf}${cqz}${inversionAlpha};`;
-              }
-            }
-            else
-            {
-              if (layeredMode && layeredFound == huntTotal)
-              {
-                callObj.hunting.cqz = "mixed";
-                cqzBg = `${cqz}${layeredAlpha};`;
-                cqz = bold;
-              }
-              else if (layeredMode && layeredWorkedFound == huntTotal)
-              {
-                callObj.hunting.cqz = "mixed-worked";
-                cqzConf = `${unconf}${cqz}${layeredAlpha};`;
-              }
-              else
-              {
-                callObj.hunting.cqz = "hunted";
-                cqzBg = `${cqz}${inversionAlpha};`;
-                cqz = bold;
-              }
-            }
-          }
-        }
-
-        // Hunting for ITU Zones
-        if (huntITUz.checked == true)
-        {
-          var huntTotal = callObj.ituza.length;
-          var huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
-
-          for (index in callObj.ituza)
-          {
-            var hash = callObj.ituza[index] + workHashSuffix;
-            var layeredHash = layeredMode && (callObj.ituza[index] + layeredHashSuffix)
-
-            if (huntIndex && hash in huntIndex.ituz) huntFound++;
-            if (layeredMode && layeredHash in huntIndex.ituz) layeredFound++;
-            if (workedIndex && hash in workedIndex.ituz) workedFound++;
-            if (layeredMode && layeredHash in workedIndex.ituz) layeredWorkedFound++;
-          }
-          if (huntFound != huntTotal)
-          {
-            shouldAlert = true;
-            callObj.reason.push("ituz");
-
-            if (workedIndex && workedFound == huntTotal)
-            {
-              if (layeredMode && layeredFound == huntTotal)
-              {
-                callObj.hunting.ituz = "worked-and-mixed";
-                ituzConf = `${layeredUnconf}${ituz}${layeredUnconfAlpha};`;
-                ituzBg = `${ituz}${layeredInversionAlpha}`;
-                ituz = bold;
-              }
-              else
-              {
-                callObj.hunting.ituz = "worked";
-                ituzConf = `${unconf}${ituz}${inversionAlpha};`;
-              }
-            }
-            else
-            {
-              if (layeredMode && layeredFound == huntTotal)
-              {
-                callObj.hunting.ituz = "mixed";
-                ituzBg = `${ituz}${layeredAlpha};`;
-                ituz = bold;
-              }
-              else if (layeredMode && layeredWorkedFound == huntTotal)
-              {
-                callObj.hunting.ituz = "mixed-worked";
-                ituzConf = `${unconf}${ituz}${layeredAlpha};`;
-              }
-              else
-              {
-                callObj.hunting.ituz = "hunted";
-                ituzBg = `${ituz}${inversionAlpha};`;
-                ituz = bold;
-              }
-            }
-          }
-        }
-
-        // Hunting for WPX (Prefixes)
-        if (huntPX.checked == true && callObj.px)
-        {
-          var hash = String(callObj.px) + workHashSuffix;
-          var layeredHash = layeredMode && (String(callObj.px) + layeredHashSuffix)
-
-          if (huntIndex && !(hash in huntIndex.px))
-          {
-            shouldAlert = true;
-
-            callObj.reason.push("wpx");
-
-            if (workedIndex && hash in workedIndex.px)
-            {
-              if (layeredMode && layeredHash in huntIndex.px)
-              {
-                callObj.hunting.wpx = "worked-and-mixed";
-                wpxConf = `${layeredUnconf}${wpx}${layeredUnconfAlpha};`;
-                wpxBg = `${wpx}${layeredInversionAlpha}`;
-                wpx = bold;
-              }
-              else
-              {
-                callObj.hunting.wpx = "worked";
-                wpxConf = `${unconf}${wpx}${inversionAlpha};`;
-              }
-            }
-            else
-            {
-              if (layeredMode && layeredHash in huntIndex.px)
-              {
-                callObj.hunting.wpx = "mixed";
-                wpxBg = `${wpx}${layeredAlpha};`;
-                wpx = bold;
-              }
-              else if (layeredMode && layeredHash in workedIndex.px)
-              {
-                callObj.hunting.wpx = "mixed-worked";
-                wpxConf = `${unconf}${wpx}${layeredAlpha};`;
-              }
-              else
-              {
-                callObj.hunting.wpx = "hunted";
-                wpxBg = `${wpx}${inversionAlpha};`;
-                wpx = bold;
-              }
-            }
-          }
-        }
-
-        // Hunting for Continents
-        if (huntCont.checked == true && callObj.cont)
-        {
-          var hash = String(callObj.cont) + workHashSuffix;
-          var layeredHash = layeredMode && (String(callObj.cont) + layeredHashSuffix)
-
-          if (huntIndex && !(hash in huntIndex.cont))
-          {
-            shouldAlert = true;
-
-            callObj.reason.push("cont");
-
-            if (workedIndex && hash in workedIndex.cont)
-            {
-              if (layeredMode && layeredHash in huntIndex.cont)
-              {
-                callObj.hunting.cont = "worked-and-mixed";
-                contConf = `${layeredUnconf}${cont}${layeredUnconfAlpha};`;
-                contBg = `${cont}${layeredInversionAlpha}`;
-                cont = bold;
-              }
-              else
-              {
-                callObj.hunting.cont = "worked";
-                contConf = `${unconf}${cont}${inversionAlpha};`;
-              }
-            }
-            else
-            {
-              if (layeredMode && layeredHash in huntIndex.cont)
-              {
-                callObj.hunting.cont = "mixed";
-                contBg = `${cont}${layeredAlpha};`;
-                cont = bold;
-              }
-              else if (layeredMode && layeredHash in workedIndex.cont)
-              {
-                callObj.hunting.cont = "mixed-worked";
-                contConf = `${unconf}${cont}${layeredAlpha};`;
-              }
-              else
-              {
-                callObj.hunting.cont = "hunted";
-                contBg = `${cont}${inversionAlpha};`;
-                cont = bold;
-              }
-            }
-          }
-        }
-      }
-
-      // Station is calling us
-      if (callObj.DXcall == window.opener.myDEcall)
-      {
-        callingBg = "#0000FF" + inversionAlpha;
-        calling = "#FFFF00;text-shadow: 0px 0px 2px #FFFF00";
-      }
-      else if (callObj.CQ == true && g_rosterSettings.cqOnly == false)
-      {
-        callingBg = calling + inversionAlpha;
-        calling = bold;
-      }
-
-      // Assemble all styles
-      colorObject.call = "style='" + callConf + "background-color:" + callBg + ";color:" +
-        call + ";" + callPointer + "'";
-      colorObject.grid = "style='" + gridConf + "background-color:" + gridBg + ";color:" + grid + ";cursor:pointer'";
-      colorObject.calling = "style='" + callingConf + "background-color:" + callingBg + ";color:" + calling + "'";
-      colorObject.dxcc = "style='" + dxccConf + "background-color:" + dxccBg + ";color:" + dxcc + "'";
-      colorObject.state = "style='" + stateConf + "background-color:" + stateBg + ";color:" + state + "'";
-      colorObject.cnty = "style='" + cntyConf + "background-color:" + cntyBg + ";color:" + cnty + "'";
-      colorObject.cont = "style='" + contConf + "background-color:" + contBg + ";color:" + cont + "'";
-      colorObject.cqz = "style='" + cqzConf + "background-color:" + cqzBg + ";color:" + cqz + "'";
-      colorObject.ituz = "style='" + ituzConf + "background-color:" + ituzBg + ";color:" + ituz + "'";
-      colorObject.px = "style='" + wpxConf + "background-color:" + wpxBg + ";color:" + wpx + "'";
-
-      // Just in case, don't alert if we worked this callsign alread
-      if (didWork && shouldAlert) shouldAlert = false;
-
-      callObj.shouldAlert = shouldAlert;
-
-      callObj.style = colorObject;
-
-      if (g_rosterSettings.columns.Spot)
-      {
-        callObj.spot = window.opener.getSpotTime(
-          callObj.DEcall + callObj.mode + callObj.band + callObj.grid
-        );
-        if (callObj.spot == null)
-        {
-          callObj.spot = { when: 0, snr: 0 };
-        }
-      }
-      else
-      {
-        callObj.spot = { when: 0, snr: 0 };
-      }
-
-      modes[callObj.mode] = true;
-      bands[callObj.band] = true;
-
-      newCallList.push(callObj);
-    }
-  }
-
-  // Show the roster count in the window title
-
-  var totalCount = Object.keys(callRoster).length;
-  var visibleCount = newCallList.length;
-  var huntedCount = newCallList.filter(obj => Object.keys(obj.hunting).length > 0).length
-  var countParts = [];
-
-  if (totalCount != visibleCount)
-  {
-    countParts.push(`${totalCount} heard`);
-  }
-
-  countParts.push(`${visibleCount} in roster`);
-
-  if (huntedCount != visibleCount)
-  {
-    countParts.push(`${huntedCount} wanted`);
-  }
-
-  window.document.title = `Call Roster: ${countParts.join(" • ")}`;
-
-  // Render the roster
-
-  if (g_rosterSettings.compact == false)
-  {
-    newCallList.sort(r_sortFunction[g_rosterSettings.lastSortIndex]);
-    if (g_rosterSettings.lastSortReverse == 1)
-    {
-      newCallList.reverse();
-    }
-  }
-  else
-  {
-    // Age sort for now... make this happen Tag
-    newCallList.sort(r_sortFunction[6]).reverse();
-  }
-
-  var showBands = (Object.keys(bands).length > 1) || g_rosterSettings.columns.Band;
-  var showModes = (Object.keys(modes).length > 1) || g_rosterSettings.columns.Mode;
-
-  var worker = "";
-
-  // Render the table headers for the regular roster table
-  if (g_rosterSettings.compact == false)
-  {
-    worker = "
";
-
-    worker += "| Callsign | ";
-
-    if (showBands)
-    { worker += "Band | "; }
-
-    if (showModes)
-    { worker += "Mode | "; }
-
-    worker += "Grid | ";
-
-    if (g_rosterSettings.columns.Calling)
-    { worker += "Calling | "; }
-
-    if (g_rosterSettings.columns.Msg)
-    { worker += "Msg | "; }
-
-    if (g_rosterSettings.columns.DXCC)
-    { worker += "DXCC | "; }
-
-    if (g_rosterSettings.columns.Flag)
-    { worker += "Flag | "; }
-
-    if (g_rosterSettings.columns.State)
-    { worker += "State | "; }
-
-    if (g_rosterSettings.columns.County)
-    { worker += "County | "; }
-
-    if (g_rosterSettings.columns.Cont)
-    { worker += "Cont | "; }
-
-    if (g_rosterSettings.columns.dB)
-    { worker += "dB | "; }
-
-    if (g_rosterSettings.columns.Freq)
-    { worker += "Freq | "; }
-
-    if (g_rosterSettings.columns.DT)
-    { worker += "DT | "; }
-
-    if (g_rosterSettings.columns.Dist)
-    {
-      worker += "Dist(" +
-        window.opener.distanceUnit.value.toLowerCase() + ") | ";
-    }
-
-    if (g_rosterSettings.columns.Azim)
-    { worker += "Azim | "; }
-
-    if (g_rosterSettings.columns.CQz)
-    { worker += "CQz | "; }
-
-    if (g_rosterSettings.columns.ITUz)
-    { worker += "ITUz | "; }
-
-    if (g_rosterSettings.columns.PX)
-    { worker += "PX | "; }
-
-    if (window.opener.g_callsignLookups.lotwUseEnable == true && g_rosterSettings.columns.LoTW)
-    { worker += "LoTW | "; }
-
-    if (window.opener.g_callsignLookups.eqslUseEnable == true && g_rosterSettings.columns.eQSL)
-    { worker += "eQSL | "; }
-
-    if (window.opener.g_callsignLookups.oqrsUseEnable == true && g_rosterSettings.columns.OQRS)
-    { worker += "OQRS | "; }
-
-    if (g_rosterSettings.columns.Spot)
-    { worker += "Spot | "; }
-
-    if (g_rosterSettings.columns.Life)
-    { worker += "Life | "; }
-
-    if (g_rosterSettings.columns.OAMS)
-    { worker += "OAMS | "; }
-
-    if (g_rosterSettings.columns.Age)
-    { worker += "Age | "; }
-  }
-  // No headers for compact roster table
-  else
-  {
-    worker = "
";
-    RosterTable.innerHTML = worker;
-  }
-  else
-  {
-    RosterTable.innerHTML = worker + "";
-  }
-
-  var dirPath = window.opener.g_scriptDir;
-  var scriptExists = false;
-  var script = "cr-alert.sh";
-
-  try
-  {
-    if (fs.existsSync(dirPath))
-    {
-      if (window.opener.g_platform == "windows")
-      {
-        script = "cr-alert.bat";
-      }
-      if (
-        fs.existsSync(dirPath + script) &&
-        g_rosterSettings.realtime == false
-      )
-      {
-        scriptExists = true;
-        scriptIcon.innerHTML =
-          "" +
-          (window.opener.g_crScript == 1
-            ? "Script Enabled"
-            : "Script Disabled") +
-          "
";
-        scriptIcon.style.display = "block";
-      }
-      else
-      {
-        scriptIcon.style.display = "none";
-      }
-    }
-  }
-  catch (e) {}
-
-  if (shouldAlert > 0)
-  {
-    if (window.opener.g_classicAlerts.huntRoster == true)
-    {
-      var notify = window.opener.huntRosterNotify.value;
-      if (notify == "0")
-      {
-        var media = window.opener.huntRosterNotifyMedia.value;
-        if (media != "none") window.opener.playAlertMediaFile(media);
-      }
-      else if (notify == "1")
-      {
-        window.opener.speakAlertString(
-          window.opener.huntRosterNotifyWord.value
-        );
-      }
-    }
-
-    if (
-      g_rosterSettings.realtime == false &&
-      scriptExists &&
-      window.opener.g_crScript == 1
-    )
-    {
-      try
-      {
-        fs.writeFileSync(
-          dirPath + "cr-alert.json",
-          JSON.stringify(g_scriptReport, null, 2)
-        );
-
-        var thisProc = dirPath + script;
-        var cp = require("child_process");
-        var child = cp.spawn(thisProc, [], {
-          detached: true,
-          cwd: dirPath.slice(0, -1),
-          stdio: ["ignore", "ignore", "ignore"]
-        });
-        child.unref();
-      }
-      catch (e)
-      {
-        conosle.log(e);
-      }
-      g_scriptReport = Object();
-    }
-    else g_scriptReport = Object();
-  }
+  var rosterSettings = prepareRosterSettings();
+  processRosterFiltering(callRoster, rosterSettings);
+  processRosterHunting(callRoster, rosterSettings);
+  renderRoster(callRoster, rosterSettings);
+  sendAlerts(callRoster, rosterSettings);
 }
 
 function realtimeRoster()
diff --git a/package.nw/lib/roster/prepareRosterSettings.js b/package.nw/lib/roster/prepareRosterSettings.js
new file mode 100644
index 00000000..5aa845b5
--- /dev/null
+++ b/package.nw/lib/roster/prepareRosterSettings.js
@@ -0,0 +1,83 @@
+function prepareRosterSettings()
+{
+  var rosterSettings = {
+    bands: {},
+    modes: {},
+    callMode: g_rosterSettings.callsign,
+    onlyHits: false,
+    isAwardTracker: false,
+    now: timeNowSec()
+  }
+
+  if (rosterSettings.callMode == "hits")
+  {
+    rosterSettings.callMode = "all"
+    rosterSettings.onlyHits = true;
+  }
+  if (referenceNeed.value == LOGBOOK_AWARD_TRACKER)
+  {
+    rosterSettings.callMode = "all";
+    rosterSettings.onlyHits = false;
+    rosterSettings.isAwardTracker = true;
+    g_rosterSettings.huntNeed = "confirmed";
+  }
+  // this appears to be determine if we should show the OAMS column
+  // if the user is not in offline mode and has OAMS enabled, this could
+  // be it's own function maybe?
+  rosterSettings.canMsg =
+    window.opener.g_mapSettings.offlineMode == false &&
+    window.opener.g_appSettings.gtShareEnable == "true" &&
+    window.opener.g_appSettings.gtMsgEnable == "true";
+
+  // The following 3 sections deal with QSLing, do we break them out
+  // individually or lump them into a qslUser function that sets
+  // all three at the same time?
+  // this section is for LoTW users, can be a function
+  if (window.opener.g_callsignLookups.lotwUseEnable == true)
+  {
+    usesLoTWDiv.style.display = "";
+    if (g_rosterSettings.usesLoTW == true)
+    {
+      maxLoTW.style.display = "";
+      maxLoTWView.style.display = "";
+    }
+    else
+    {
+      maxLoTW.style.display = "none";
+      maxLoTWView.style.display = "none";
+    }
+  }
+  else
+  {
+    usesLoTWDiv.style.display = "none";
+    maxLoTW.style.display = "none";
+    maxLoTWView.style.display = "none";
+  }
+
+  if (g_rosterSettings.huntNeed == "mixed")
+  {
+    rosterSettings.huntIndex = g_confirmed;
+    rosterSettings.workedIndex = g_worked;
+    rosterSettings.layeredMode = LAYERED_MODE_FOR[String(g_rosterSettings.reference)];
+  }
+  else if (g_rosterSettings.huntNeed == "worked")
+  {
+    rosterSettings.huntIndex = g_worked;
+    rosterSettings.workedIndex = false;
+    rosterSettings.layeredMode = false;
+  }
+  else if (g_rosterSettings.huntNeed == "confirmed")
+  {
+    rosterSettings.huntIndex = g_confirmed;
+    rosterSettings.workedIndex = g_worked;
+    rosterSettings.layeredMode = false;
+  }
+  else
+  {
+    rosterSettings.huntIndex = false;
+    rosterSettings.workedIndex = false;
+    rosterSettings.layeredMode = false;
+  }
+
+  return rosterSettings
+}
diff --git a/package.nw/lib/roster/processFiltering.js b/package.nw/lib/roster/processFiltering.js
new file mode 100644
index 00000000..9d6c21ea
--- /dev/null
+++ b/package.nw/lib/roster/processFiltering.js
@@ -0,0 +1,363 @@
+function processRosterFiltering()
+{
+  // First loop, exclude calls, mostly based on "Exceptions" settings
+  // this whole section is full of individual if's that could be broken out
+  for (var callHash in callRoster)
+  {
+    var entry = callRoster[callHash];
+    var callObj = entry.callObj;
+
+    var call = entry.DEcall;
+
+    entry.tx = true;
+    callObj.shouldAlert = false;
+    callObj.reason = Array();
+    callObj.awardReason = "Callsign";
+
+    if (now - callObj.age > window.opener.g_mapSettings.rosterTime)
+    {
+      entry.tx = false;
+      entry.alerted = false;
+      callObj.qrz = false;
+      callObj.reset = true;
+      continue;
+    }
+    if (window.opener.g_instances[callObj.instance].crEnable == false)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (call in g_blockedCalls)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (
+      entry.DXcall + " from All" in g_blockedCQ ||
+      entry.DXcall + " from " + window.opener.g_dxccToAltName[callObj.dxcc] in g_blockedCQ
+    )
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (callObj.dxcc in g_blockedDxcc)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.cqOnly == true && callObj.CQ == false)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.useRegex && g_rosterSettings.callsignRegex.length > 0)
+    {
+      try
+      {
+        if (!call.match(g_rosterSettings.callsignRegex))
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+      catch (e) {}
+    }
+    if (g_rosterSettings.requireGrid == true && callObj.grid.length != 4)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMinDB == true && entry.message.SR < g_rosterSettings.minDb)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMaxDT == true && Math.abs(entry.message.DT) > g_rosterSettings.maxDT)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMinFreq == true && entry.message.DF < g_rosterSettings.minFreq)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMaxFreq == true && entry.message.DF > g_rosterSettings.maxFreq)
+    {
+      entry.tx = false;
+      continue;
+    }
+
+    if (g_rosterSettings.noMsg == true)
+    {
+      try
+      {
+        if (callObj.msg.match(g_rosterSettings.noMsgValue))
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+      catch (e) {}
+    }
+    if (g_rosterSettings.onlyMsg == true)
+    {
+      try
+      {
+        if (!callObj.msg.match(g_rosterSettings.onlyMsgValue))
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+      catch (e) {}
+    }
+
+    if (callObj.dxcc == window.opener.g_myDXCC)
+    {
+      if (g_rosterSettings.noMyDxcc == true)
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+    else
+    {
+      if (g_rosterSettings.onlyMyDxcc == true)
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+
+    if (window.opener.g_callsignLookups.lotwUseEnable == true && g_rosterSettings.usesLoTW == true)
+    {
+      if (!(call in window.opener.g_lotwCallsigns))
+      {
+        entry.tx = false;
+        continue;
+      }
+      if (g_rosterSettings.maxLoTW < 27)
+      {
+        var months = (g_day - window.opener.g_lotwCallsigns[call]) / 30;
+        if (months > g_rosterSettings.maxLoTW)
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+    }
+
+    if (window.opener.g_callsignLookups.eqslUseEnable == true && g_rosterSettings.useseQSL == true)
+    {
+      if (!(call in window.opener.g_eqslCallsigns))
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+
+    if (window.opener.g_callsignLookups.oqrsUseEnable == true && g_rosterSettings.usesOQRS == true)
+    {
+      if (!(call in window.opener.g_oqrsCallsigns))
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+
+    if (callMode != "all")
+    {
+      if (entry.DXcall == "CQ DX" && callObj.dxcc == window.opener.g_myDXCC)
+      {
+        entry.tx = false;
+        continue;
+      }
+
+      var hash = hashMaker(call, callObj, g_rosterSettings.reference);
+      if (callMode == "worked" && hash in g_worked.call)
+      {
+        entry.tx = false;
+        continue;
+      }
+      if (callMode == "confirmed" && hash in g_confirmed.call)
+      {
+        entry.tx = false;
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "grid")
+      {
+        var hash = hashMaker(callObj.grid.substr(0, 4),
+          callObj, g_rosterSettings.reference);
+        if (huntIndex && hash in huntIndex.grid)
+        {
+          entry.tx = false;
+          continue;
+        }
+        if (callObj.grid.length == 0)
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+      if (g_rosterSettings.hunting == "dxcc")
+      {
+        var hash = hashMaker(String(callObj.dxcc),
+          callObj, g_rosterSettings.reference);
+
+        if (huntIndex && (hash in huntIndex.dxcc))
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "dxccs" && r_currentDXCCs != -1)
+      {
+        if (callObj.dxcc != r_currentDXCCs)
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+
+      if (g_rosterSettings.hunting == "wpx")
+      {
+        if (String(callObj.px) == null)
+        {
+          entry.tx = false;
+          continue;
+        }
+        var hash = hashMaker(String(callObj.px),
+          callObj, g_rosterSettings.reference);
+
+        if (huntIndex && (hash in huntIndex.px))
+        {
+          entry.tx = false;
+          continue;
+        }
+
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "cq")
+      {
+        var huntTotal = callObj.cqza.length;
+        if (huntTotal == 0 || !huntIndex)
+        {
+          entry.tx = false;
+          continue;
+        }
+        var huntFound = 0;
+        for (index in callObj.cqza)
+        {
+          var hash = hashMaker(callObj.cqza[index], callObj, g_rosterSettings.reference);
+
+          if (hash in huntIndex.cqz) huntFound++;
+        }
+        if (huntFound == huntTotal)
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "itu")
+      {
+        var huntTotal = callObj.ituza.length;
+        if (huntTotal == 0 || !huntIndex)
+        {
+          entry.tx = false;
+          continue;
+        }
+        var huntFound = 0;
+        for (index in callObj.ituza)
+        {
+          var hash = hashMaker(callObj.ituza[index], callObj, g_rosterSettings.reference);
+
+          if (hash in huntIndex.ituz) huntFound++;
+        }
+        if (huntFound == huntTotal)
+        {
+          entry.tx = false;
+          continue;
+        }
+
+        if (callObj.grid.length == 0)
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "usstates" && window.opener.g_callsignLookups.ulsUseEnable == true)
+      {
+        var state = callObj.state;
+        var finalDxcc = callObj.dxcc;
+        if (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6)
+        {
+          if (state in window.opener.g_StateData)
+          {
+            var hash = hashMaker(state, callObj, g_rosterSettings.reference);
+
+            if (huntIndex && hash in huntIndex.state)
+            {
+              entry.tx = false;
+              continue;
+            }
+          }
+          else entry.tx = false;
+        }
+        else entry.tx = false;
+
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "usstate" && g_currentUSCallsigns)
+      {
+        if (call in g_currentUSCallsigns)
+        {
+          // Do Nothing
+        }
+        else
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+    }
+    if (isAwardTracker)
+    {
+      var tx = false;
+      var baseHash = hashMaker("", callObj, g_rosterSettings.reference);
+
+      for (var award in g_awardTracker)
+      {
+        if (g_awardTracker[award].enable)
+        {
+          tx = testAward(award, callObj, baseHash);
+          if (tx)
+          {
+            var x = g_awardTracker[award];
+
+            // TODO: Move award reason out of exclusions code?
+            callObj.awardReason =
+              g_awards[x.sponsor].awards[x.name].tooltip +
+              " (" +
+              g_awards[x.sponsor].sponsor +
+              ")";
+
+            break;
+          }
+        }
+      }
+      entry.tx = tx;
+    }
+  }
+}
diff --git a/package.nw/lib/roster/processHunting.js b/package.nw/lib/roster/processHunting.js
new file mode 100644
index 00000000..c569e08f
--- /dev/null
+++ b/package.nw/lib/roster/processHunting.js
@@ -0,0 +1,706 @@
+function processHunting()
+{
+  // these vars, do they rely on anything between the top and here?
+  // if not could they be put in the var list at the beginning?
+  var hasGtPin = false;
+
+  var newCallList = Array();
+  var inversionAlpha = "DD";
+  var row = "#000000";
+  var bold = "#000000;font-weight: bold;";
+  var unconf = "background-clip:padding-box;box-shadow: 0 0 7px 3px inset ";
+  var layeredAlpha = "77";
+  var layeredInversionAlpha = "66";
+  var layeredUnconf = "background-clip:padding-box;box-shadow: 0 0 4px 2px inset ";
+  var layeredUnconfAlpha = "AA";
+
+  // TODO: Hunting results might be used to filter, based on the "Callsigns: Only Wanted" option,
+  //       so maybe we can move this loop first, and add a check to the filtering loop?
+
+  // Second loop, hunting and highlighting
+  for (var callHash in callRoster)
+  {
+    var entry = callRoster[callHash];
+    var callObj = entry.callObj;
+
+    // Special case check for called station
+    if (callObj.qrz == true && entry.tx == false)
+    {
+      // The instance has to be enabled
+      if (window.opener.g_instances[callObj.instance].crEnable == true)
+      {
+        // Calling us, but we wouldn't normally display
+        // If they are not ignored or we're in a QSO with them, var it through
+
+        // TODO: This is here because it's after the filtering stage
+        if ((!(entry.DEcall in g_blockedCalls) && !(callObj.dxcc in g_blockedDxcc)) ||
+          window.opener.g_instances[callObj.instance].status.DXcall == entry.DEcall)
+        {
+          entry.tx = true;
+        }
+      }
+    }
+
+    // Only render entries with `tx == true`, ignore the rest
+    if (callObj.dxcc != -1 && entry.tx == true)
+    {
+      // In layered mode ("Hunting: mixed") the workHashSuffix becomes a more stricter 'live band',
+      // while the layered suffix is a broader 'mixed band'
+      var workHashSuffix, layeredHashSuffix;
+      if (layeredMode)
+      {
+        workHashSuffix = hashMaker("", callObj, layeredMode);
+        layeredHashSuffix = hashMaker("", callObj, g_rosterSettings.reference);
+      }
+      else
+      {
+        workHashSuffix = hashMaker("", callObj, g_rosterSettings.reference);
+        layeredHashSuffix = false
+      }
+      var workHash = workHashSuffix; // TODO: Remove after replacing all occurrences with Suffix
+
+      var callsign = entry.DEcall;
+
+      callObj.hunting = {}
+      callObj.callFlags = {}
+
+      var colorObject = Object();
+
+      var callPointer = callObj.CQ == true ? "cursor:pointer" : "";
+
+      var didWork = false;
+
+      var call = "#FFFF00";
+      var grid = "#00FFFF";
+      var calling = "#90EE90";
+      var dxcc = "#FFA500";
+      var state = "#90EE90";
+      var cnty = "#CCDD00";
+      var cont = "#00DDDD";
+      var cqz = "#DDDDDD";
+      var ituz = "#DDDDDD";
+      var wpx = "#FFFF00";
+
+      hasGtPin = false;
+      var shouldAlert = false;
+      var callBg, gridBg, callingBg, dxccBg, stateBg, cntyBg, contBg, cqzBg, ituzBg, wpxBg, gtBg;
+      var callConf, gridConf, callingConf, dxccConf, stateConf, cntyConf, contConf, cqzConf, ituzConf, wpxConf;
+
+      callBg = gridBg = callingBg = dxccBg = stateBg = cntyBg = contBg = cqzBg = ituzBg = wpxBg = gtBg = row;
+
+      callConf = gridConf = callingConf = dxccConf = stateConf = cntyConf = contConf = cqzConf = ituzConf = wpxConf =
+        "";
+
+      var hash = callsign + workHashSuffix;
+      var layeredHash = layeredHashSuffix && (callsign + layeredHashSuffix)
+
+      // Call worked in current logbook settings, regardless of hunting mode
+      if (hash in g_worked.call)
+      {
+        callObj.callFlags.worked = true;
+        didWork = true;
+        callConf = `${unconf}${call}${inversionAlpha};`;
+
+        if (hash in g_confirmed.call)
+        {
+          callObj.callFlags.confirmed = true;
+          callPointer = "text-decoration: line-through; ";
+          callConf = "";
+        }
+      }
+
+      // Calls that have OAMS chat support
+      if (
+        callsign in window.opener.g_gtCallsigns &&
+        window.opener.g_gtCallsigns[callsign] in window.opener.g_gtFlagPins &&
+        window.opener.g_gtFlagPins[window.opener.g_gtCallsigns[callsign]].canmsg == true
+      )
+      {
+        callObj.callFlags.oams = true;
+        // grab the CID
+        colorObject.gt = window.opener.g_gtCallsigns[callsign];
+        hasGtPin = true;
+      }
+      else
+      {
+        colorObject.gt = 0;
+      }
+
+      // We only do hunt highlighting when showing all entries
+      // This means "Callsigns: All Traffic", "Callsigns: All Traffic/Only Wanted" and "Logbook: Award Tracker"
+      // There is no highlighting in other modes
+      if (callMode == "all")
+      {
+        // Skip when "only new calls"
+        // Questions: Move to the first loop? Why only skip new calls in "all traffic" and not other modes?
+        if (allOnlyNew.checked == true && didWork && callObj.qrz == false)
+        {
+          entry.tx = false;
+          continue;
+        }
+
+        // Hunting for callsigns
+        if (huntCallsign.checked == true)
+        {
+          var hash = callsign + workHashSuffix;
+          var layeredHash = layeredMode && (callsign + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.call))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("call");
+
+            if (workedIndex && hash in workedIndex.call)
+            {
+              if (layeredMode && layeredHash in huntIndex.call)
+              {
+                callObj.hunting.call = "worked-and-mixed";
+                callConf = `${layeredUnconf}${call}${layeredUnconfAlpha};`;
+                callBg = `${call}${layeredInversionAlpha}`;
+                call = bold;
+              }
+              // /* Currently we don't have a way to figure out
+              //  * if the call is worked only in this band or also others,
+              //  * so we cannot cover this particular combination
+              //  * and have to default to just showing it as plain "worked"
+              //  */
+              // else if (layeredMode && layeredHash in workedIndex.call)
+              // {
+              //   callObj.hunting.call = "worked-and-mixed-worked";
+              //   callConf = `${layeredUnconf}${call}${layeredAlpha};`;
+              // }
+              else
+              {
+                callObj.hunting.call = "worked";
+                callConf = `${unconf}${call}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.call)
+              {
+                callObj.hunting.call = "mixed";
+                callBg = `${call}${layeredAlpha};`;
+                call = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.call)
+              {
+                callObj.hunting.call = "mixed-worked";
+                callConf = `${unconf}${call}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.call = "hunted";
+                callBg = `${call}${inversionAlpha};`;
+                call = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for "stations calling you"
+        if (huntQRZ.checked == true && callObj.qrz == true)
+        {
+          callObj.callFlags.calling = true
+          shouldAlert = true;
+          callObj.reason.push("qrz");
+        }
+
+        // Hunting for stations with OAMS
+        if (huntOAMS.checked == true && hasGtPin == true)
+        {
+          callObj.hunting.oams = "hunted";
+          shouldAlert = true;
+          callObj.reason.push("oams");
+        }
+
+        // Hunting for grids
+        if (huntGrid.checked == true && callObj.grid.length > 1)
+        {
+          var hash = callObj.grid.substr(0, 4) + workHashSuffix;
+          var layeredHash = layeredMode && (callObj.grid.substr(0, 4) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.grid))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("grid");
+
+            if (workedIndex && hash in workedIndex.grid)
+            {
+              if (layeredMode && layeredHash in huntIndex.grid)
+              {
+                callObj.hunting.grid = "worked-and-mixed";
+                gridConf = `${layeredUnconf}${grid}${layeredUnconfAlpha};`;
+                gridBg = `${grid}${layeredInversionAlpha}`;
+                grid = bold;
+              }
+              else
+              {
+                callObj.hunting.grid = "worked";
+                gridConf = `${unconf}${grid}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.grid)
+              {
+                callObj.hunting.grid = "mixed";
+                gridBg = `${grid}${layeredAlpha};`;
+                grid = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.grid)
+              {
+                callObj.hunting.grid = "mixed-worked";
+                gridConf = `${unconf}${grid}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.grid = "hunted";
+                gridBg = `${grid}${inversionAlpha};`;
+                grid = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for DXCC
+        if (huntDXCC.checked == true)
+        {
+          var hash = String(callObj.dxcc) + workHashSuffix;
+          var layeredHash = layeredMode && (String(callObj.dxcc) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.dxcc))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("dxcc");
+
+            if (workedIndex && hash in workedIndex.dxcc)
+            {
+              if (layeredMode && layeredHash in huntIndex.dxcc)
+              {
+                callObj.hunting.dxcc = "worked-and-mixed";
+                dxccConf = `${layeredUnconf}${dxcc}${layeredUnconfAlpha};`;
+                dxccBg = `${dxcc}${layeredInversionAlpha}`;
+                dxcc = bold;
+              }
+              else
+              {
+                callObj.hunting.dxcc = "worked";
+                dxccConf = `${unconf}${dxcc}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.dxcc)
+              {
+                callObj.hunting.dxcc = "mixed";
+                dxccBg = `${dxcc}${layeredAlpha};`;
+                dxcc = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.dxcc)
+              {
+                callObj.hunting.dxcc = "mixed-worked";
+                dxccConf = `${unconf}${dxcc}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.dxcc = "hunted";
+                dxccBg = `${dxcc}${inversionAlpha};`;
+                dxcc = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for US States
+        if (huntState.checked == true && window.opener.g_callsignLookups.ulsUseEnable == true)
+        {
+          var stateSearch = callObj.state;
+          var finalDxcc = callObj.dxcc;
+          if (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6)
+          {
+            if (stateSearch in window.opener.g_StateData)
+            {
+              var hash = stateSearch + workHashSuffix;
+              var layeredHash = layeredMode && (stateSearch + layeredHashSuffix)
+
+              if (huntIndex && !(hash in huntIndex.state))
+              {
+                shouldAlert = true;
+
+                callObj.reason.push("state");
+
+                if (workedIndex && hash in workedIndex.state)
+                {
+                  if (layeredMode && layeredHash in huntIndex.state)
+                  {
+                    callObj.hunting.state = "worked-and-mixed";
+                    stateConf = `${layeredUnconf}${state}${layeredUnconfAlpha};`;
+                    stateBg = `${state}${layeredInversionAlpha}`;
+                    state = bold;
+                  }
+                  else
+                  {
+                    callObj.hunting.state = "worked";
+                    stateConf = `${unconf}${state}${inversionAlpha};`;
+                  }
+                }
+                else
+                {
+                  if (layeredMode && layeredHash in huntIndex.state)
+                  {
+                    callObj.hunting.state = "mixed";
+                    stateBg = `${state}${layeredAlpha};`;
+                    state = bold;
+                  }
+                  else if (layeredMode && layeredHash in workedIndex.state)
+                  {
+                    callObj.hunting.state = "mixed-worked";
+                    stateConf = `${unconf}${state}${layeredAlpha};`;
+                  }
+                  else
+                  {
+                    callObj.hunting.state = "hunted";
+                    stateBg = `${state}${inversionAlpha};`;
+                    state = bold;
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        // Hunting for US Counties
+        if (huntCounty.checked == true && window.opener.g_callsignLookups.ulsUseEnable == true)
+        {
+          var finalDxcc = callObj.dxcc;
+          if (
+            callObj.cnty &&
+            (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6 || finalDxcc == 202) &&
+            callObj.cnty.length > 0
+          )
+          {
+            var hash = callObj.cnty + (layeredMode ? layeredHashSuffix : workHashSuffix);
+
+            if ((huntIndex && !(hash in huntIndex.cnty)) || callObj.qual == false)
+            {
+              if (callObj.qual == false)
+              {
+                var counties = window.opener.g_zipToCounty[callObj.zipcode];
+                var foundHit = false;
+                for (var cnt in counties)
+                {
+                  var hh = counties[cnt] + workHash;
+                  callObj.cnty = counties[cnt];
+                  if (huntIndex && !(hh in huntIndex.cnty))
+                  {
+                    foundHit = true;
+                    break;
+                  }
+                }
+                if (foundHit) shouldAlert = true;
+              }
+              else
+              {
+                shouldAlert = true;
+              }
+
+              if (shouldAlert)
+              {
+                callObj.reason.push("cnty");
+
+                if (workedIndex && hash in workedIndex.cnty)
+                {
+                  callObj.hunting.cnty = "worked";
+                  cntyConf = `${unconf}${cnty}${inversionAlpha};`;
+                }
+                else
+                {
+                  callObj.hunting.cnty = "hunted";
+                  cntyBg = `${cnty}${inversionAlpha}`;
+                  cnty = bold;
+                }
+              }
+            }
+          }
+        }
+
+        // Hunting for CQ Zones
+        if (huntCQz.checked == true)
+        {
+          var huntTotal = callObj.cqza.length;
+          var huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
+
+          for (index in callObj.cqza)
+          {
+            var hash = callObj.cqza[index] + workHashSuffix;
+            var layeredHash = layeredMode && (callObj.cqza[index] + layeredHashSuffix)
+
+            if (huntIndex && hash in huntIndex.cqz) huntFound++;
+            if (layeredMode && layeredHash in huntIndex.cqz) layeredFound++;
+            if (workedIndex && hash in workedIndex.cqz) workedFound++;
+            if (layeredMode && layeredHash in workedIndex.cqz) layeredWorkedFound++;
+          }
+          if (huntFound != huntTotal)
+          {
+            shouldAlert = true;
+            callObj.reason.push("cqz");
+
+            if (workedIndex && workedFound == huntTotal)
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.cqz = "worked-and-mixed";
+                cqzConf = `${layeredUnconf}${cqz}${layeredUnconfAlpha};`;
+                cqzBg = `${cqz}${layeredInversionAlpha}`;
+                cqz = bold;
+              }
+              else
+              {
+                callObj.hunting.cqz = "worked";
+                cqzConf = `${unconf}${cqz}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.cqz = "mixed";
+                cqzBg = `${cqz}${layeredAlpha};`;
+                cqz = bold;
+              }
+              else if (layeredMode && layeredWorkedFound == huntTotal)
+              {
+                callObj.hunting.cqz = "mixed-worked";
+                cqzConf = `${unconf}${cqz}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.cqz = "hunted";
+                cqzBg = `${cqz}${inversionAlpha};`;
+                cqz = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for ITU Zones
+        if (huntITUz.checked == true)
+        {
+          var huntTotal = callObj.ituza.length;
+          var huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
+
+          for (index in callObj.ituza)
+          {
+            var hash = callObj.ituza[index] + workHashSuffix;
+            var layeredHash = layeredMode && (callObj.ituza[index] + layeredHashSuffix)
+
+            if (huntIndex && hash in huntIndex.ituz) huntFound++;
+            if (layeredMode && layeredHash in huntIndex.ituz) layeredFound++;
+            if (workedIndex && hash in workedIndex.ituz) workedFound++;
+            if (layeredMode && layeredHash in workedIndex.ituz) layeredWorkedFound++;
+          }
+          if (huntFound != huntTotal)
+          {
+            shouldAlert = true;
+            callObj.reason.push("ituz");
+
+            if (workedIndex && workedFound == huntTotal)
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.ituz = "worked-and-mixed";
+                ituzConf = `${layeredUnconf}${ituz}${layeredUnconfAlpha};`;
+                ituzBg = `${ituz}${layeredInversionAlpha}`;
+                ituz = bold;
+              }
+              else
+              {
+                callObj.hunting.ituz = "worked";
+                ituzConf = `${unconf}${ituz}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.ituz = "mixed";
+                ituzBg = `${ituz}${layeredAlpha};`;
+                ituz = bold;
+              }
+              else if (layeredMode && layeredWorkedFound == huntTotal)
+              {
+                callObj.hunting.ituz = "mixed-worked";
+                ituzConf = `${unconf}${ituz}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.ituz = "hunted";
+                ituzBg = `${ituz}${inversionAlpha};`;
+                ituz = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for WPX (Prefixes)
+        if (huntPX.checked == true && callObj.px)
+        {
+          var hash = String(callObj.px) + workHashSuffix;
+          var layeredHash = layeredMode && (String(callObj.px) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.px))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("wpx");
+
+            if (workedIndex && hash in workedIndex.px)
+            {
+              if (layeredMode && layeredHash in huntIndex.px)
+              {
+                callObj.hunting.wpx = "worked-and-mixed";
+                wpxConf = `${layeredUnconf}${wpx}${layeredUnconfAlpha};`;
+                wpxBg = `${wpx}${layeredInversionAlpha}`;
+                wpx = bold;
+              }
+              else
+              {
+                callObj.hunting.wpx = "worked";
+                wpxConf = `${unconf}${wpx}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.px)
+              {
+                callObj.hunting.wpx = "mixed";
+                wpxBg = `${wpx}${layeredAlpha};`;
+                wpx = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.px)
+              {
+                callObj.hunting.wpx = "mixed-worked";
+                wpxConf = `${unconf}${wpx}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.wpx = "hunted";
+                wpxBg = `${wpx}${inversionAlpha};`;
+                wpx = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for Continents
+        if (huntCont.checked == true && callObj.cont)
+        {
+          var hash = String(callObj.cont) + workHashSuffix;
+          var layeredHash = layeredMode && (String(callObj.cont) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.cont))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("cont");
+
+            if (workedIndex && hash in workedIndex.cont)
+            {
+              if (layeredMode && layeredHash in huntIndex.cont)
+              {
+                callObj.hunting.cont = "worked-and-mixed";
+                contConf = `${layeredUnconf}${cont}${layeredUnconfAlpha};`;
+                contBg = `${cont}${layeredInversionAlpha}`;
+                cont = bold;
+              }
+              else
+              {
+                callObj.hunting.cont = "worked";
+                contConf = `${unconf}${cont}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.cont)
+              {
+                callObj.hunting.cont = "mixed";
+                contBg = `${cont}${layeredAlpha};`;
+                cont = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.cont)
+              {
+                callObj.hunting.cont = "mixed-worked";
+                contConf = `${unconf}${cont}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.cont = "hunted";
+                contBg = `${cont}${inversionAlpha};`;
+                cont = bold;
+              }
+            }
+          }
+        }
+      }
+
+      // Station is calling us
+      if (callObj.DXcall == window.opener.myDEcall)
+      {
+        callingBg = "#0000FF" + inversionAlpha;
+        calling = "#FFFF00;text-shadow: 0px 0px 2px #FFFF00";
+      }
+      else if (callObj.CQ == true && g_rosterSettings.cqOnly == false)
+      {
+        callingBg = calling + inversionAlpha;
+        calling = bold;
+      }
+
+      // Assemble all styles
+      colorObject.call = "style='" + callConf + "background-color:" + callBg + ";color:" +
+        call + ";" + callPointer + "'";
+      colorObject.grid = "style='" + gridConf + "background-color:" + gridBg + ";color:" + grid + ";cursor:pointer'";
+      colorObject.calling = "style='" + callingConf + "background-color:" + callingBg + ";color:" + calling + "'";
+      colorObject.dxcc = "style='" + dxccConf + "background-color:" + dxccBg + ";color:" + dxcc + "'";
+      colorObject.state = "style='" + stateConf + "background-color:" + stateBg + ";color:" + state + "'";
+      colorObject.cnty = "style='" + cntyConf + "background-color:" + cntyBg + ";color:" + cnty + "'";
+      colorObject.cont = "style='" + contConf + "background-color:" + contBg + ";color:" + cont + "'";
+      colorObject.cqz = "style='" + cqzConf + "background-color:" + cqzBg + ";color:" + cqz + "'";
+      colorObject.ituz = "style='" + ituzConf + "background-color:" + ituzBg + ";color:" + ituz + "'";
+      colorObject.px = "style='" + wpxConf + "background-color:" + wpxBg + ";color:" + wpx + "'";
+
+      // Just in case, don't alert if we worked this callsign alread
+      if (didWork && shouldAlert) shouldAlert = false;
+
+      callObj.shouldAlert = shouldAlert;
+
+      callObj.style = colorObject;
+
+      if (g_rosterSettings.columns.Spot)
+      {
+        callObj.spot = window.opener.getSpotTime(
+          callObj.DEcall + callObj.mode + callObj.band + callObj.grid
+        );
+        if (callObj.spot == null)
+        {
+          callObj.spot = { when: 0, snr: 0 };
+        }
+      }
+      else
+      {
+        callObj.spot = { when: 0, snr: 0 };
+      }
+
+      modes[callObj.mode] = true;
+      bands[callObj.band] = true;
+
+      // NOTE: This is where calls that were filtered are passed into an array that will be used for the rendering loop
+      newCallList.push(callObj);
+    }
+  }
+
+  return newCallList;
+}
diff --git a/package.nw/lib/roster/processRosterFiltering.js b/package.nw/lib/roster/processRosterFiltering.js
new file mode 100644
index 00000000..9c411429
--- /dev/null
+++ b/package.nw/lib/roster/processRosterFiltering.js
@@ -0,0 +1,369 @@
+function processRosterFiltering(callRoster, rosterSettings)
+{
+  // First loop, exclude calls, mostly based on "Exceptions" settings
+  // this whole section is full of individual if's that could be broken out
+  for (var callHash in callRoster)
+  {
+    var entry = callRoster[callHash];
+    var callObj = entry.callObj;
+
+    var call = entry.DEcall;
+
+    entry.tx = true;
+    callObj.shouldAlert = false;
+    callObj.reason = Array();
+    callObj.awardReason = "Callsign";
+
+    if (now - callObj.age > window.opener.g_mapSettings.rosterTime)
+    {
+      entry.tx = false;
+      entry.alerted = false;
+      callObj.qrz = false;
+      callObj.reset = true;
+      continue;
+    }
+    if (window.opener.g_instances[callObj.instance].crEnable == false)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (call in g_blockedCalls)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (
+      entry.DXcall + " from All" in g_blockedCQ ||
+      entry.DXcall + " from " + window.opener.g_dxccToAltName[callObj.dxcc] in g_blockedCQ
+    )
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (callObj.dxcc in g_blockedDxcc)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.cqOnly == true && callObj.CQ == false)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.useRegex && g_rosterSettings.callsignRegex.length > 0)
+    {
+      try
+      {
+        if (!call.match(g_rosterSettings.callsignRegex))
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+      catch (e) {}
+    }
+    if (g_rosterSettings.requireGrid == true && callObj.grid.length != 4)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMinDB == true && entry.message.SR < g_rosterSettings.minDb)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMaxDT == true && Math.abs(entry.message.DT) > g_rosterSettings.maxDT)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMinFreq == true && entry.message.DF < g_rosterSettings.minFreq)
+    {
+      entry.tx = false;
+      continue;
+    }
+    if (g_rosterSettings.wantMaxFreq == true && entry.message.DF > g_rosterSettings.maxFreq)
+    {
+      entry.tx = false;
+      continue;
+    }
+
+    if (g_rosterSettings.noMsg == true)
+    {
+      try
+      {
+        if (callObj.msg.match(g_rosterSettings.noMsgValue))
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+      catch (e) {}
+    }
+    if (g_rosterSettings.onlyMsg == true)
+    {
+      try
+      {
+        if (!callObj.msg.match(g_rosterSettings.onlyMsgValue))
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+      catch (e) {}
+    }
+
+    if (callObj.dxcc == window.opener.g_myDXCC)
+    {
+      if (g_rosterSettings.noMyDxcc == true)
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+    else
+    {
+      if (g_rosterSettings.onlyMyDxcc == true)
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+
+    if (window.opener.g_callsignLookups.lotwUseEnable == true && g_rosterSettings.usesLoTW == true)
+    {
+      if (!(call in window.opener.g_lotwCallsigns))
+      {
+        entry.tx = false;
+        continue;
+      }
+      if (g_rosterSettings.maxLoTW < 27)
+      {
+        var months = (g_day - window.opener.g_lotwCallsigns[call]) / 30;
+        if (months > g_rosterSettings.maxLoTW)
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+    }
+
+    if (window.opener.g_callsignLookups.eqslUseEnable == true && g_rosterSettings.useseQSL == true)
+    {
+      if (!(call in window.opener.g_eqslCallsigns))
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+
+    if (window.opener.g_callsignLookups.oqrsUseEnable == true && g_rosterSettings.usesOQRS == true)
+    {
+      if (!(call in window.opener.g_oqrsCallsigns))
+      {
+        entry.tx = false;
+        continue;
+      }
+    }
+
+    if (callMode != "all")
+    {
+      if (entry.DXcall == "CQ DX" && callObj.dxcc == window.opener.g_myDXCC)
+      {
+        entry.tx = false;
+        continue;
+      }
+
+      var hash = hashMaker(call, callObj, g_rosterSettings.reference);
+      if (callMode == "worked" && hash in g_worked.call)
+      {
+        entry.tx = false;
+        continue;
+      }
+      if (callMode == "confirmed" && hash in g_confirmed.call)
+      {
+        entry.tx = false;
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "grid")
+      {
+        var hash = hashMaker(callObj.grid.substr(0, 4),
+          callObj, g_rosterSettings.reference);
+        if (huntIndex && hash in huntIndex.grid)
+        {
+          entry.tx = false;
+          continue;
+        }
+        if (callObj.grid.length == 0)
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+      if (g_rosterSettings.hunting == "dxcc")
+      {
+        var hash = hashMaker(String(callObj.dxcc),
+          callObj, g_rosterSettings.reference);
+
+        if (huntIndex && (hash in huntIndex.dxcc))
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+
+      if (callObj.dxcc === -1)
+      {
+        entry.tx = false;
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "dxccs" && r_currentDXCCs != -1)
+      {
+        if (callObj.dxcc != r_currentDXCCs)
+        {
+          entry.tx = false;
+          continue;
+        }
+      }
+
+      if (g_rosterSettings.hunting == "wpx")
+      {
+        if (String(callObj.px) == null)
+        {
+          entry.tx = false;
+          continue;
+        }
+        var hash = hashMaker(String(callObj.px),
+          callObj, g_rosterSettings.reference);
+
+        if (huntIndex && (hash in huntIndex.px))
+        {
+          entry.tx = false;
+          continue;
+        }
+
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "cq")
+      {
+        var huntTotal = callObj.cqza.length;
+        if (huntTotal == 0 || !huntIndex)
+        {
+          entry.tx = false;
+          continue;
+        }
+        var huntFound = 0;
+        for (index in callObj.cqza)
+        {
+          var hash = hashMaker(callObj.cqza[index], callObj, g_rosterSettings.reference);
+
+          if (hash in huntIndex.cqz) huntFound++;
+        }
+        if (huntFound == huntTotal)
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "itu")
+      {
+        var huntTotal = callObj.ituza.length;
+        if (huntTotal == 0 || !huntIndex)
+        {
+          entry.tx = false;
+          continue;
+        }
+        var huntFound = 0;
+        for (index in callObj.ituza)
+        {
+          var hash = hashMaker(callObj.ituza[index], callObj, g_rosterSettings.reference);
+
+          if (hash in huntIndex.ituz) huntFound++;
+        }
+        if (huntFound == huntTotal)
+        {
+          entry.tx = false;
+          continue;
+        }
+
+        if (callObj.grid.length == 0)
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "usstates" && window.opener.g_callsignLookups.ulsUseEnable == true)
+      {
+        var state = callObj.state;
+        var finalDxcc = callObj.dxcc;
+        if (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6)
+        {
+          if (state in window.opener.g_StateData)
+          {
+            var hash = hashMaker(state, callObj, g_rosterSettings.reference);
+
+            if (huntIndex && hash in huntIndex.state)
+            {
+              entry.tx = false;
+              continue;
+            }
+          }
+          else entry.tx = false;
+        }
+        else entry.tx = false;
+
+        continue;
+      }
+
+      if (g_rosterSettings.hunting == "usstate" && g_currentUSCallsigns)
+      {
+        if (call in g_currentUSCallsigns)
+        {
+          // Do Nothing
+        }
+        else
+        {
+          entry.tx = false;
+          continue;
+        }
+        continue;
+      }
+    }
+    if (isAwardTracker)
+    {
+      var tx = false;
+      var baseHash = hashMaker("", callObj, g_rosterSettings.reference);
+
+      for (var award in g_awardTracker)
+      {
+        if (g_awardTracker[award].enable)
+        {
+          tx = testAward(award, callObj, baseHash);
+          if (tx)
+          {
+            var x = g_awardTracker[award];
+
+            // TODO: Move award reason out of exclusions code?
+            callObj.awardReason =
+              g_awards[x.sponsor].awards[x.name].tooltip +
+              " (" +
+              g_awards[x.sponsor].sponsor +
+              ")";
+
+            break;
+          }
+        }
+      }
+      entry.tx = tx;
+    }
+  }
+}
diff --git a/package.nw/lib/roster/processRosterHunting.js b/package.nw/lib/roster/processRosterHunting.js
new file mode 100644
index 00000000..6adaeab2
--- /dev/null
+++ b/package.nw/lib/roster/processRosterHunting.js
@@ -0,0 +1,700 @@
+function processRosterHunting(callRoster, rosterSettings)
+{
+  // these vars, do they rely on anything between the top and here?
+  // if not could they be put in the var list at the beginning?
+  var hasGtPin = false;
+
+  var inversionAlpha = "DD";
+  var row = "#000000";
+  var bold = "#000000;font-weight: bold;";
+  var unconf = "background-clip:padding-box;box-shadow: 0 0 7px 3px inset ";
+  var layeredAlpha = "77";
+  var layeredInversionAlpha = "66";
+  var layeredUnconf = "background-clip:padding-box;box-shadow: 0 0 4px 2px inset ";
+  var layeredUnconfAlpha = "AA";
+
+  // TODO: Hunting results might be used to filter, based on the "Callsigns: Only Wanted" option,
+  //       so maybe we can move this loop first, and add a check to the filtering loop?
+
+  // Second loop, hunting and highlighting
+  for (var callHash in callRoster)
+  {
+    var entry = callRoster[callHash];
+    var callObj = entry.callObj;
+
+    // Special case check for called station
+    if (callObj.qrz == true && entry.tx == false)
+    {
+      // The instance has to be enabled
+      if (window.opener.g_instances[callObj.instance].crEnable == true)
+      {
+        // Calling us, but we wouldn't normally display
+        // If they are not ignored or we're in a QSO with them, var it through
+
+        // TODO: This is here because it's after the filtering stage
+        if ((!(entry.DEcall in g_blockedCalls) && !(callObj.dxcc in g_blockedDxcc)) ||
+          window.opener.g_instances[callObj.instance].status.DXcall == entry.DEcall)
+        {
+          entry.tx = true;
+        }
+      }
+    }
+
+    // Only render entries with `tx == true`, ignore the rest
+    if (entry.tx == true)
+    {
+      // In layered mode ("Hunting: mixed") the workHashSuffix becomes a more stricter 'live band',
+      // while the layered suffix is a broader 'mixed band'
+      var workHashSuffix, layeredHashSuffix;
+      if (layeredMode)
+      {
+        workHashSuffix = hashMaker("", callObj, layeredMode);
+        layeredHashSuffix = hashMaker("", callObj, g_rosterSettings.reference);
+      }
+      else
+      {
+        workHashSuffix = hashMaker("", callObj, g_rosterSettings.reference);
+        layeredHashSuffix = false
+      }
+      var workHash = workHashSuffix; // TODO: Remove after replacing all occurrences with Suffix
+
+      var callsign = entry.DEcall;
+
+      callObj.hunting = {}
+      callObj.callFlags = {}
+
+      var colorObject = Object();
+
+      var callPointer = callObj.CQ == true ? "cursor:pointer" : "";
+
+      var didWork = false;
+
+      var call = "#FFFF00";
+      var grid = "#00FFFF";
+      var calling = "#90EE90";
+      var dxcc = "#FFA500";
+      var state = "#90EE90";
+      var cnty = "#CCDD00";
+      var cont = "#00DDDD";
+      var cqz = "#DDDDDD";
+      var ituz = "#DDDDDD";
+      var wpx = "#FFFF00";
+
+      hasGtPin = false;
+      var shouldAlert = false;
+      var callBg, gridBg, callingBg, dxccBg, stateBg, cntyBg, contBg, cqzBg, ituzBg, wpxBg, gtBg;
+      var callConf, gridConf, callingConf, dxccConf, stateConf, cntyConf, contConf, cqzConf, ituzConf, wpxConf;
+
+      callBg = gridBg = callingBg = dxccBg = stateBg = cntyBg = contBg = cqzBg = ituzBg = wpxBg = gtBg = row;
+
+      callConf = gridConf = callingConf = dxccConf = stateConf = cntyConf = contConf = cqzConf = ituzConf = wpxConf =
+        "";
+
+      var hash = callsign + workHashSuffix;
+      var layeredHash = layeredHashSuffix && (callsign + layeredHashSuffix)
+
+      // Call worked in current logbook settings, regardless of hunting mode
+      if (hash in g_worked.call)
+      {
+        callObj.callFlags.worked = true;
+        didWork = true;
+        callConf = `${unconf}${call}${inversionAlpha};`;
+
+        if (hash in g_confirmed.call)
+        {
+          callObj.callFlags.confirmed = true;
+          callPointer = "text-decoration: line-through; ";
+          callConf = "";
+        }
+      }
+
+      // Calls that have OAMS chat support
+      if (
+        callsign in window.opener.g_gtCallsigns &&
+        window.opener.g_gtCallsigns[callsign] in window.opener.g_gtFlagPins &&
+        window.opener.g_gtFlagPins[window.opener.g_gtCallsigns[callsign]].canmsg == true
+      )
+      {
+        callObj.callFlags.oams = true;
+        // grab the CID
+        colorObject.gt = window.opener.g_gtCallsigns[callsign];
+        hasGtPin = true;
+      }
+      else
+      {
+        colorObject.gt = 0;
+      }
+
+      // We only do hunt highlighting when showing all entries
+      // This means "Callsigns: All Traffic", "Callsigns: All Traffic/Only Wanted" and "Logbook: Award Tracker"
+      // There is no highlighting in other modes
+      if (callMode == "all")
+      {
+        // Skip when "only new calls"
+        // Questions: Move to the first loop? Why only skip new calls in "all traffic" and not other modes?
+        if (allOnlyNew.checked == true && didWork && callObj.qrz == false)
+        {
+          entry.tx = false;
+          continue;
+        }
+
+        // Hunting for callsigns
+        if (huntCallsign.checked == true)
+        {
+          var hash = callsign + workHashSuffix;
+          var layeredHash = layeredMode && (callsign + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.call))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("call");
+
+            if (workedIndex && hash in workedIndex.call)
+            {
+              if (layeredMode && layeredHash in huntIndex.call)
+              {
+                callObj.hunting.call = "worked-and-mixed";
+                callConf = `${layeredUnconf}${call}${layeredUnconfAlpha};`;
+                callBg = `${call}${layeredInversionAlpha}`;
+                call = bold;
+              }
+              // /* Currently we don't have a way to figure out
+              //  * if the call is worked only in this band or also others,
+              //  * so we cannot cover this particular combination
+              //  * and have to default to just showing it as plain "worked"
+              //  */
+              // else if (layeredMode && layeredHash in workedIndex.call)
+              // {
+              //   callObj.hunting.call = "worked-and-mixed-worked";
+              //   callConf = `${layeredUnconf}${call}${layeredAlpha};`;
+              // }
+              else
+              {
+                callObj.hunting.call = "worked";
+                callConf = `${unconf}${call}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.call)
+              {
+                callObj.hunting.call = "mixed";
+                callBg = `${call}${layeredAlpha};`;
+                call = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.call)
+              {
+                callObj.hunting.call = "mixed-worked";
+                callConf = `${unconf}${call}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.call = "hunted";
+                callBg = `${call}${inversionAlpha};`;
+                call = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for "stations calling you"
+        if (huntQRZ.checked == true && callObj.qrz == true)
+        {
+          callObj.callFlags.calling = true
+          shouldAlert = true;
+          callObj.reason.push("qrz");
+        }
+
+        // Hunting for stations with OAMS
+        if (huntOAMS.checked == true && hasGtPin == true)
+        {
+          callObj.hunting.oams = "hunted";
+          shouldAlert = true;
+          callObj.reason.push("oams");
+        }
+
+        // Hunting for grids
+        if (huntGrid.checked == true && callObj.grid.length > 1)
+        {
+          var hash = callObj.grid.substr(0, 4) + workHashSuffix;
+          var layeredHash = layeredMode && (callObj.grid.substr(0, 4) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.grid))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("grid");
+
+            if (workedIndex && hash in workedIndex.grid)
+            {
+              if (layeredMode && layeredHash in huntIndex.grid)
+              {
+                callObj.hunting.grid = "worked-and-mixed";
+                gridConf = `${layeredUnconf}${grid}${layeredUnconfAlpha};`;
+                gridBg = `${grid}${layeredInversionAlpha}`;
+                grid = bold;
+              }
+              else
+              {
+                callObj.hunting.grid = "worked";
+                gridConf = `${unconf}${grid}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.grid)
+              {
+                callObj.hunting.grid = "mixed";
+                gridBg = `${grid}${layeredAlpha};`;
+                grid = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.grid)
+              {
+                callObj.hunting.grid = "mixed-worked";
+                gridConf = `${unconf}${grid}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.grid = "hunted";
+                gridBg = `${grid}${inversionAlpha};`;
+                grid = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for DXCC
+        if (huntDXCC.checked == true)
+        {
+          var hash = String(callObj.dxcc) + workHashSuffix;
+          var layeredHash = layeredMode && (String(callObj.dxcc) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.dxcc))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("dxcc");
+
+            if (workedIndex && hash in workedIndex.dxcc)
+            {
+              if (layeredMode && layeredHash in huntIndex.dxcc)
+              {
+                callObj.hunting.dxcc = "worked-and-mixed";
+                dxccConf = `${layeredUnconf}${dxcc}${layeredUnconfAlpha};`;
+                dxccBg = `${dxcc}${layeredInversionAlpha}`;
+                dxcc = bold;
+              }
+              else
+              {
+                callObj.hunting.dxcc = "worked";
+                dxccConf = `${unconf}${dxcc}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.dxcc)
+              {
+                callObj.hunting.dxcc = "mixed";
+                dxccBg = `${dxcc}${layeredAlpha};`;
+                dxcc = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.dxcc)
+              {
+                callObj.hunting.dxcc = "mixed-worked";
+                dxccConf = `${unconf}${dxcc}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.dxcc = "hunted";
+                dxccBg = `${dxcc}${inversionAlpha};`;
+                dxcc = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for US States
+        if (huntState.checked == true && window.opener.g_callsignLookups.ulsUseEnable == true)
+        {
+          var stateSearch = callObj.state;
+          var finalDxcc = callObj.dxcc;
+          if (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6)
+          {
+            if (stateSearch in window.opener.g_StateData)
+            {
+              var hash = stateSearch + workHashSuffix;
+              var layeredHash = layeredMode && (stateSearch + layeredHashSuffix)
+
+              if (huntIndex && !(hash in huntIndex.state))
+              {
+                shouldAlert = true;
+
+                callObj.reason.push("state");
+
+                if (workedIndex && hash in workedIndex.state)
+                {
+                  if (layeredMode && layeredHash in huntIndex.state)
+                  {
+                    callObj.hunting.state = "worked-and-mixed";
+                    stateConf = `${layeredUnconf}${state}${layeredUnconfAlpha};`;
+                    stateBg = `${state}${layeredInversionAlpha}`;
+                    state = bold;
+                  }
+                  else
+                  {
+                    callObj.hunting.state = "worked";
+                    stateConf = `${unconf}${state}${inversionAlpha};`;
+                  }
+                }
+                else
+                {
+                  if (layeredMode && layeredHash in huntIndex.state)
+                  {
+                    callObj.hunting.state = "mixed";
+                    stateBg = `${state}${layeredAlpha};`;
+                    state = bold;
+                  }
+                  else if (layeredMode && layeredHash in workedIndex.state)
+                  {
+                    callObj.hunting.state = "mixed-worked";
+                    stateConf = `${unconf}${state}${layeredAlpha};`;
+                  }
+                  else
+                  {
+                    callObj.hunting.state = "hunted";
+                    stateBg = `${state}${inversionAlpha};`;
+                    state = bold;
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        // Hunting for US Counties
+        if (huntCounty.checked == true && window.opener.g_callsignLookups.ulsUseEnable == true)
+        {
+          var finalDxcc = callObj.dxcc;
+          if (
+            callObj.cnty &&
+            (finalDxcc == 291 || finalDxcc == 110 || finalDxcc == 6 || finalDxcc == 202) &&
+            callObj.cnty.length > 0
+          )
+          {
+            var hash = callObj.cnty + (layeredMode ? layeredHashSuffix : workHashSuffix);
+
+            if ((huntIndex && !(hash in huntIndex.cnty)) || callObj.qual == false)
+            {
+              if (callObj.qual == false)
+              {
+                var counties = window.opener.g_zipToCounty[callObj.zipcode];
+                var foundHit = false;
+                for (var cnt in counties)
+                {
+                  var hh = counties[cnt] + workHash;
+                  callObj.cnty = counties[cnt];
+                  if (huntIndex && !(hh in huntIndex.cnty))
+                  {
+                    foundHit = true;
+                    break;
+                  }
+                }
+                if (foundHit) shouldAlert = true;
+              }
+              else
+              {
+                shouldAlert = true;
+              }
+
+              if (shouldAlert)
+              {
+                callObj.reason.push("cnty");
+
+                if (workedIndex && hash in workedIndex.cnty)
+                {
+                  callObj.hunting.cnty = "worked";
+                  cntyConf = `${unconf}${cnty}${inversionAlpha};`;
+                }
+                else
+                {
+                  callObj.hunting.cnty = "hunted";
+                  cntyBg = `${cnty}${inversionAlpha}`;
+                  cnty = bold;
+                }
+              }
+            }
+          }
+        }
+
+        // Hunting for CQ Zones
+        if (huntCQz.checked == true)
+        {
+          var huntTotal = callObj.cqza.length;
+          var huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
+
+          for (index in callObj.cqza)
+          {
+            var hash = callObj.cqza[index] + workHashSuffix;
+            var layeredHash = layeredMode && (callObj.cqza[index] + layeredHashSuffix)
+
+            if (huntIndex && hash in huntIndex.cqz) huntFound++;
+            if (layeredMode && layeredHash in huntIndex.cqz) layeredFound++;
+            if (workedIndex && hash in workedIndex.cqz) workedFound++;
+            if (layeredMode && layeredHash in workedIndex.cqz) layeredWorkedFound++;
+          }
+          if (huntFound != huntTotal)
+          {
+            shouldAlert = true;
+            callObj.reason.push("cqz");
+
+            if (workedIndex && workedFound == huntTotal)
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.cqz = "worked-and-mixed";
+                cqzConf = `${layeredUnconf}${cqz}${layeredUnconfAlpha};`;
+                cqzBg = `${cqz}${layeredInversionAlpha}`;
+                cqz = bold;
+              }
+              else
+              {
+                callObj.hunting.cqz = "worked";
+                cqzConf = `${unconf}${cqz}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.cqz = "mixed";
+                cqzBg = `${cqz}${layeredAlpha};`;
+                cqz = bold;
+              }
+              else if (layeredMode && layeredWorkedFound == huntTotal)
+              {
+                callObj.hunting.cqz = "mixed-worked";
+                cqzConf = `${unconf}${cqz}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.cqz = "hunted";
+                cqzBg = `${cqz}${inversionAlpha};`;
+                cqz = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for ITU Zones
+        if (huntITUz.checked == true)
+        {
+          var huntTotal = callObj.ituza.length;
+          var huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
+
+          for (index in callObj.ituza)
+          {
+            var hash = callObj.ituza[index] + workHashSuffix;
+            var layeredHash = layeredMode && (callObj.ituza[index] + layeredHashSuffix)
+
+            if (huntIndex && hash in huntIndex.ituz) huntFound++;
+            if (layeredMode && layeredHash in huntIndex.ituz) layeredFound++;
+            if (workedIndex && hash in workedIndex.ituz) workedFound++;
+            if (layeredMode && layeredHash in workedIndex.ituz) layeredWorkedFound++;
+          }
+          if (huntFound != huntTotal)
+          {
+            shouldAlert = true;
+            callObj.reason.push("ituz");
+
+            if (workedIndex && workedFound == huntTotal)
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.ituz = "worked-and-mixed";
+                ituzConf = `${layeredUnconf}${ituz}${layeredUnconfAlpha};`;
+                ituzBg = `${ituz}${layeredInversionAlpha}`;
+                ituz = bold;
+              }
+              else
+              {
+                callObj.hunting.ituz = "worked";
+                ituzConf = `${unconf}${ituz}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredFound == huntTotal)
+              {
+                callObj.hunting.ituz = "mixed";
+                ituzBg = `${ituz}${layeredAlpha};`;
+                ituz = bold;
+              }
+              else if (layeredMode && layeredWorkedFound == huntTotal)
+              {
+                callObj.hunting.ituz = "mixed-worked";
+                ituzConf = `${unconf}${ituz}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.ituz = "hunted";
+                ituzBg = `${ituz}${inversionAlpha};`;
+                ituz = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for WPX (Prefixes)
+        if (huntPX.checked == true && callObj.px)
+        {
+          var hash = String(callObj.px) + workHashSuffix;
+          var layeredHash = layeredMode && (String(callObj.px) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.px))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("wpx");
+
+            if (workedIndex && hash in workedIndex.px)
+            {
+              if (layeredMode && layeredHash in huntIndex.px)
+              {
+                callObj.hunting.wpx = "worked-and-mixed";
+                wpxConf = `${layeredUnconf}${wpx}${layeredUnconfAlpha};`;
+                wpxBg = `${wpx}${layeredInversionAlpha}`;
+                wpx = bold;
+              }
+              else
+              {
+                callObj.hunting.wpx = "worked";
+                wpxConf = `${unconf}${wpx}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.px)
+              {
+                callObj.hunting.wpx = "mixed";
+                wpxBg = `${wpx}${layeredAlpha};`;
+                wpx = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.px)
+              {
+                callObj.hunting.wpx = "mixed-worked";
+                wpxConf = `${unconf}${wpx}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.wpx = "hunted";
+                wpxBg = `${wpx}${inversionAlpha};`;
+                wpx = bold;
+              }
+            }
+          }
+        }
+
+        // Hunting for Continents
+        if (huntCont.checked == true && callObj.cont)
+        {
+          var hash = String(callObj.cont) + workHashSuffix;
+          var layeredHash = layeredMode && (String(callObj.cont) + layeredHashSuffix)
+
+          if (huntIndex && !(hash in huntIndex.cont))
+          {
+            shouldAlert = true;
+
+            callObj.reason.push("cont");
+
+            if (workedIndex && hash in workedIndex.cont)
+            {
+              if (layeredMode && layeredHash in huntIndex.cont)
+              {
+                callObj.hunting.cont = "worked-and-mixed";
+                contConf = `${layeredUnconf}${cont}${layeredUnconfAlpha};`;
+                contBg = `${cont}${layeredInversionAlpha}`;
+                cont = bold;
+              }
+              else
+              {
+                callObj.hunting.cont = "worked";
+                contConf = `${unconf}${cont}${inversionAlpha};`;
+              }
+            }
+            else
+            {
+              if (layeredMode && layeredHash in huntIndex.cont)
+              {
+                callObj.hunting.cont = "mixed";
+                contBg = `${cont}${layeredAlpha};`;
+                cont = bold;
+              }
+              else if (layeredMode && layeredHash in workedIndex.cont)
+              {
+                callObj.hunting.cont = "mixed-worked";
+                contConf = `${unconf}${cont}${layeredAlpha};`;
+              }
+              else
+              {
+                callObj.hunting.cont = "hunted";
+                contBg = `${cont}${inversionAlpha};`;
+                cont = bold;
+              }
+            }
+          }
+        }
+      }
+
+      // Station is calling us
+      if (callObj.DXcall == window.opener.myDEcall)
+      {
+        callingBg = "#0000FF" + inversionAlpha;
+        calling = "#FFFF00;text-shadow: 0px 0px 2px #FFFF00";
+      }
+      else if (callObj.CQ == true && g_rosterSettings.cqOnly == false)
+      {
+        callingBg = calling + inversionAlpha;
+        calling = bold;
+      }
+
+      // Assemble all styles
+      colorObject.call = "style='" + callConf + "background-color:" + callBg + ";color:" +
+        call + ";" + callPointer + "'";
+      colorObject.grid = "style='" + gridConf + "background-color:" + gridBg + ";color:" + grid + ";cursor:pointer'";
+      colorObject.calling = "style='" + callingConf + "background-color:" + callingBg + ";color:" + calling + "'";
+      colorObject.dxcc = "style='" + dxccConf + "background-color:" + dxccBg + ";color:" + dxcc + "'";
+      colorObject.state = "style='" + stateConf + "background-color:" + stateBg + ";color:" + state + "'";
+      colorObject.cnty = "style='" + cntyConf + "background-color:" + cntyBg + ";color:" + cnty + "'";
+      colorObject.cont = "style='" + contConf + "background-color:" + contBg + ";color:" + cont + "'";
+      colorObject.cqz = "style='" + cqzConf + "background-color:" + cqzBg + ";color:" + cqz + "'";
+      colorObject.ituz = "style='" + ituzConf + "background-color:" + ituzBg + ";color:" + ituz + "'";
+      colorObject.px = "style='" + wpxConf + "background-color:" + wpxBg + ";color:" + wpx + "'";
+
+      // Just in case, don't alert if we worked this callsign alread
+      if (didWork && shouldAlert) shouldAlert = false;
+
+      callObj.shouldAlert = shouldAlert;
+
+      callObj.style = colorObject;
+
+      if (g_rosterSettings.columns.Spot)
+      {
+        callObj.spot = window.opener.getSpotTime(
+          callObj.DEcall + callObj.mode + callObj.band + callObj.grid
+        );
+        if (callObj.spot == null)
+        {
+          callObj.spot = { when: 0, snr: 0 };
+        }
+      }
+      else
+      {
+        callObj.spot = { when: 0, snr: 0 };
+      }
+
+      modes[callObj.mode] = true;
+      bands[callObj.band] = true;
+    }
+  }
+}
diff --git a/package.nw/lib/roster/renderCompactRoster.js b/package.nw/lib/roster/renderCompactRoster.js
new file mode 100644
index 00000000..271a0bcc
--- /dev/null
+++ b/package.nw/lib/roster/renderCompactRoster.js
@@ -0,0 +1,55 @@
+function renderCompactRosterHeaders()
+{
+  return "";
+}
diff --git a/package.nw/lib/roster/renderNormalRoster.js b/package.nw/lib/roster/renderNormalRoster.js
new file mode 100644
index 00000000..80fa5a84
--- /dev/null
+++ b/package.nw/lib/roster/renderNormalRoster.js
@@ -0,0 +1,400 @@
+function renderNormalRosterHeaders()
+{
+  var worker = ""
+  worker = "";
+
+  worker += "| Callsign | ";
+
+  if (showBands)
+  { worker += "Band | "; }
+
+  if (showModes)
+  { worker += "Mode | "; }
+
+  worker += "Grid | ";
+
+  if (g_rosterSettings.columns.Calling)
+  { worker += "Calling | "; }
+
+  if (g_rosterSettings.columns.Msg)
+  { worker += "Msg | "; }
+
+  if (g_rosterSettings.columns.DXCC)
+  { worker += "DXCC | "; }
+
+  if (g_rosterSettings.columns.Flag)
+  { worker += "Flag | "; }
+
+  if (g_rosterSettings.columns.State)
+  { worker += "State | "; }
+
+  if (g_rosterSettings.columns.County)
+  { worker += "County | "; }
+
+  if (g_rosterSettings.columns.Cont)
+  { worker += "Cont | "; }
+
+  if (g_rosterSettings.columns.dB)
+  { worker += "dB | "; }
+
+  if (g_rosterSettings.columns.Freq)
+  { worker += "Freq | "; }
+
+  if (g_rosterSettings.columns.DT)
+  { worker += "DT | "; }
+
+  if (g_rosterSettings.columns.Dist)
+  {
+    worker += "Dist(" +
+      window.opener.distanceUnit.value.toLowerCase() + ") | ";
+  }
+
+  if (g_rosterSettings.columns.Azim)
+  { worker += "Azim | "; }
+
+  if (g_rosterSettings.columns.CQz)
+  { worker += "CQz | "; }
+
+  if (g_rosterSettings.columns.ITUz)
+  { worker += "ITUz | "; }
+
+  if (g_rosterSettings.columns.PX)
+  { worker += "PX | "; }
+
+  if (window.opener.g_callsignLookups.lotwUseEnable == true && g_rosterSettings.columns.LoTW)
+  { worker += "LoTW | "; }
+
+  if (window.opener.g_callsignLookups.eqslUseEnable == true && g_rosterSettings.columns.eQSL)
+  { worker += "eQSL | "; }
+
+  if (window.opener.g_callsignLookups.oqrsUseEnable == true && g_rosterSettings.columns.OQRS)
+  { worker += "OQRS | "; }
+
+  if (g_rosterSettings.columns.Spot)
+  { worker += "Spot | "; }
+
+  if (g_rosterSettings.columns.Life)
+  { worker += "Life | "; }
+
+  if (g_rosterSettings.columns.OAMS)
+  { worker += "OAMS | "; }
+
+  if (g_rosterSettings.columns.Age)
+  { worker += "Age | "; }
+
+  return worker
+}
+
+function renderNormalRosterRow(callObj)
+{
+  var thisCall = callObj.DEcall;
+  var acks = window.opener.g_acknowledgedCalls;
+
+  var thisHash = thisCall + callObj.band + callObj.mode;
+  var callStr = thisCall.formatCallsign()
+  if (acks[thisCall])
+  {
+    callStr = `${callStr} 
`
+    callObj.awardReason += ` - ${acks[thisCall].message}`
+  }
+
+  var worker = "";
+  worker +=
+    "| " +
+    callStr +
+    " | ";
+
+  if (showBands)
+  {
+    worker +=
+      "" +
+      callObj.band +
+      " | ";
+  }
+  if (showModes)
+  {
+    var color = "888888";
+    if (callObj.mode in g_modeColors)
+    { color = g_modeColors[callObj.mode]; }
+    worker +=
+      "" + callObj.mode + " | ";
+  }
+
+  worker +=
+    "" +
+    grid +
+    " | ";
+  if (g_rosterSettings.columns.Calling)
+  {
+    var lookString = callObj.CQ ? "name='CQ'" : "name='Calling'";
+    worker +=
+      "" +
+      callObj.DXcall.formatCallsign() +
+      " | ";
+  }
+  if (g_rosterSettings.columns.Msg)
+  { worker += "" + callObj.msg + " | "; }
+
+  if (g_rosterSettings.columns.DXCC)
+  {
+    worker +=
+      "" +
+      window.opener.g_dxccToAltName[callObj.dxcc] + " | ";
+  }
+  if (g_rosterSettings.columns.Flag)
+  {
+    worker +=
+      "  | ";
+  }
+  if (g_rosterSettings.columns.State)
+  {
+    worker +=
+      "" +
+      (callObj.state ? callObj.state.substr(3) : "") +
+      " | ";
+  }
+  if (g_rosterSettings.columns.County)
+  {
+    worker +=
+      "" +
+      (callObj.cnty
+        ? (callObj.qual ? "" : "~ ") +
+          window.opener.g_cntyToCounty[callObj.cnty] +
+          (callObj.qual ? "" : " ~")
+        : "") +
+      " | ";
+  }
+  if (g_rosterSettings.columns.Cont)
+  {
+    worker +=
+      "" +
+      (callObj.cont ? callObj.cont : "") +
+      " | ";
+  }
+
+  if (g_rosterSettings.columns.dB)
+  {
+    worker +=
+      "" +
+      callObj.RSTsent +
+      " | ";
+  }
+  if (g_rosterSettings.columns.Freq)
+  { worker += "" + callObj.delta + " | "; }
+  if (g_rosterSettings.columns.DT)
+  { worker += "" + callObj.dt + " | "; }
+  if (g_rosterSettings.columns.Dist)
+  {
+    worker +=
+      "" +
+      parseInt(
+        callObj.distance *
+          MyCircle.validateRadius(window.opener.distanceUnit.value)
+      ) +
+      " | ";
+  }
+  if (g_rosterSettings.columns.Azim)
+  {
+    worker +=
+      "" +
+      parseInt(callObj.heading) +
+      " | ";
+  }
+
+  if (g_rosterSettings.columns.CQz)
+  {
+    worker +=
+      "" +
+      callObj.cqza.join(",") +
+      " | ";
+  }
+  if (g_rosterSettings.columns.ITUz)
+  {
+    worker +=
+      "" +
+      callObj.ituza.join(",") +
+      " | ";
+  }
+
+  if (g_rosterSettings.columns.PX)
+  {
+    worker +=
+      "" +
+      (callObj.px ? callObj.px : "") +
+      " | ";
+  }
+
+  if (
+    window.opener.g_callsignLookups.lotwUseEnable == true &&
+    g_rosterSettings.columns.LoTW
+  )
+  {
+    if (thisCall in window.opener.g_lotwCallsigns)
+    {
+      if (g_rosterSettings.maxLoTW < 27)
+      {
+        var months = (g_day - window.opener.g_lotwCallsigns[thisCall]) / 30;
+        if (months > g_rosterSettings.maxLoTW)
+        {
+          worker +=
+            "? | ";
+        }
+        else
+        {
+          worker +=
+            "✔ | ";
+        }
+      }
+      else
+      {
+        worker +=
+          "✔ | ";
+      }
+    }
+    else worker += " | ";
+  }
+  if (
+    window.opener.g_callsignLookups.eqslUseEnable == true &&
+    g_rosterSettings.columns.eQSL
+  )
+  {
+    worker +=
+      "" +
+      (thisCall in window.opener.g_eqslCallsigns ? "✔" : "") +
+      " | ";
+  }
+  if (
+    window.opener.g_callsignLookups.oqrsUseEnable == true &&
+    g_rosterSettings.columns.OQRS
+  )
+  {
+    worker +=
+      "" +
+      (thisCall in window.opener.g_oqrsCallsigns ? "✔" : "") +
+      " | ";
+  }
+
+  if (g_rosterSettings.columns.Spot)
+  {
+    worker +=
+      "" +
+      spotString +
+      " | ";
+  }
+  if (g_rosterSettings.columns.Life)
+  {
+    worker +=
+      "" +
+      (timeNowSec() - callObj.life).toDHMS() +
+      " | ";
+  }
+
+  if (g_rosterSettings.columns.OAMS)
+  {
+    if (callObj.style.gt != 0)
+    {
+      if (callObj.reason.includes("oams"))
+      {
+        worker +=
+          "  | ";
+      }
+      else
+      {
+        worker +=
+          "  | ";
+      }
+    }
+    else worker += " | ";
+  }
+
+  if (g_rosterSettings.columns.Age)
+  {
+    worker +=
+      "" +
+      (timeNowSec() - callObj.age).toDHMS() +
+      " | ";
+  }
+
+  worker += "
";
+
+  return worker;
+}
+
+function renderNormalRosterFooter()
+{
+  return "
";
+}
diff --git a/package.nw/lib/roster/renderRoster.js b/package.nw/lib/roster/renderRoster.js
new file mode 100644
index 00000000..20f4be9a
--- /dev/null
+++ b/package.nw/lib/roster/renderRoster.js
@@ -0,0 +1,102 @@
+function renderRoster(callRoster, rosterSettings)
+{
+  // eQSL - function
+  if (window.opener.g_callsignLookups.eqslUseEnable == true) useseQSLDiv.style.display = "";
+  else useseQSLDiv.style.display = "none";
+
+  // OQRS - function
+  if (window.opener.g_callsignLookups.oqrsUseEnable == true) usesOQRSDiv.style.display = "";
+  else usesOQRSDiv.style.display = "none";
+
+  // dealing with spots
+  if (g_rosterSettings.columns.Spot == true) onlySpotDiv.style.display = "";
+  else onlySpotDiv.style.display = "none";
+
+  // callmode (all or only new)
+  if (rosterSettings.callMode == "all") allOnlyNewDiv.style.display = "";
+  else allOnlyNewDiv.style.display = "none";
+
+  // Show the roster count in the window title
+
+  var visibleCallList = callRoster.filter(entry => entry.tx);
+
+  var totalCount = Object.keys(callRoster).length;
+  var visibleCount = visibleCallList.length;
+  var huntedCount = visibleCallList.filter(obj => Object.keys(obj.hunting).length > 0).length
+  var countParts = [];
+
+  if (totalCount != visibleCount)
+  {
+    countParts.push(`${totalCount} heard`);
+  }
+
+  countParts.push(`${visibleCount} in roster`);
+
+  if (huntedCount != visibleCount)
+  {
+    countParts.push(`${huntedCount} wanted`);
+  }
+
+  window.document.title = `Call Roster: ${countParts.join(" • ")}`;
+
+  if (g_rosterSettings.compact == false)
+  {
+    visibleCallList.sort(r_sortFunction[g_rosterSettings.lastSortIndex]);
+    if (g_rosterSettings.lastSortReverse == 1)
+    {
+      visibleCallList.reverse();
+    }
+  }
+  else
+  {
+    // Age sort for now... make this happen Tag
+    visibleCallList.sort(r_sortFunction[6]).reverse();
+  }
+
+  var showBands = (Object.keys(rosterSettings.bands).length > 1) || g_rosterSettings.columns.Band;
+  var showModes = (Object.keys(rosterSettings.modes).length > 1) || g_rosterSettings.columns.Mode;
+
+  var worker = g_rosterSettings.compact ? renderCompactRosterHeaders() : renderNormalRosterHeaders()
+
+  // Third loop: render all rows
+  for (var x in visibleCallList)
+  {
+    var callObj = visibleCallList[x];
+
+    // TODO: This is filtering
+    if (callObj.shouldAlert == false && onlyHits == true && callObj.qrz == false)
+    { continue; }
+
+    var spotString = "";
+    if (g_rosterSettings.columns.Spot && callObj.qrz == false)
+    {
+      spotString = getSpotString(callObj);
+      // TODO: This is filtering
+      if (g_rosterSettings.onlySpot && spotString == "") continue;
+    }
+    var grid = callObj.grid.length > 1 ? callObj.grid.substr(0, 4) : "-";
+
+    var geo = window.opener.g_worldGeoData[window.opener.g_dxccToGeoData[callObj.dxcc]];
+    var cqzone = grid in window.opener.g_gridToCQZone ? window.opener.g_gridToCQZone[grid].join(", ") : "-";
+    var ituzone = grid in window.opener.g_gridToITUZone ? window.opener.g_gridToITUZone[grid].join(", ") : "-";
+    var thisCall = callObj.DEcall;
+
+    if (thisCall.match("^[A-Z][0-9][A-Z](/w+)?$"))
+    { callObj.style.call = "class='oneByOne'"; }
+    if (thisCall == window.opener.g_instances[callObj.instance].status.DXcall)
+    {
+      if (window.opener.g_instances[callObj.instance].status.TxEnabled == 1)
+      {
+        callObj.style.call = "class='dxCalling'";
+      }
+      else
+      {
+        callObj.style.call = "class='dxCaller'";
+      }
+    }
+
+    worker += g_rosterSettings.compact ? renderCompactRosterRow(callObj) : renderNormalRosterRow(callObj)
+  }
+
+  RosterTable.innerHTML = g_rosterSettings.compact ? renderCompactRosterFooter() : renderNormalRosterFooter()
+}
diff --git a/package.nw/lib/roster/sendAlerts.js b/package.nw/lib/roster/sendAlerts.js
new file mode 100644
index 00000000..ab814f16
--- /dev/null
+++ b/package.nw/lib/roster/sendAlerts.js
@@ -0,0 +1,135 @@
+function sendAlerts(callRoster, rosterSettings)
+{
+  var dirPath = window.opener.g_scriptDir;
+  var scriptExists = false;
+  var script = "cr-alert.sh";
+
+  var shouldAlert = 0;
+
+  for (var callObj in callRoster)
+  { if (!callObj.tx) continue }
+
+  // TODO: Get rid of realtime
+  if (g_rosterSettings.realtime == false)
+  {
+    var call = callObj.DEcall;
+    g_scriptReport[call] = Object.assign({}, callObj);
+    g_scriptReport[call].dxccName =
+        window.opener.g_dxccToAltName[callObj.dxcc];
+    g_scriptReport[call].distance = parseInt(
+      callObj.distance *
+          MyCircle.validateRadius(window.opener.distanceUnit.value)
+    );
+
+    delete g_scriptReport[call].DEcall;
+    g_scriptReport[call].rect = null;
+    delete g_scriptReport[call].rect;
+    delete g_scriptReport[call].style;
+    delete g_scriptReport[call].wspr;
+    delete g_scriptReport[call].qso;
+    delete g_scriptReport[call].instance;
+
+    if (callMode != "all")
+    {
+      g_scriptReport[call].shouldAlert = true;
+      g_scriptReport[call].reason.push(g_rosterSettings.hunting);
+    }
+  }
+
+  if (
+    callObj.alerted == false &&
+      callMode == "all" &&
+      callObj.shouldAlert == true
+  )
+  {
+    callObj.alerted = true;
+    shouldAlert++;
+  }
+  else if (callObj.alerted == false && callMode != "all")
+  {
+    callObj.alerted = true;
+    shouldAlert++;
+  }
+
+  callObj.shouldAlert = false;
+}
+
+// NOTE: Ring alerts if needed
+try
+{
+  if (fs.existsSync(dirPath))
+  {
+    if (window.opener.g_platform == "windows")
+    {
+      script = "cr-alert.bat";
+    }
+    if (
+      fs.existsSync(dirPath + script) &&
+        g_rosterSettings.realtime == false
+    )
+    {
+      scriptExists = true;
+      scriptIcon.innerHTML =
+          "" +
+          (window.opener.g_crScript == 1
+            ? "Script Enabled"
+            : "Script Disabled") +
+          "
";
+      scriptIcon.style.display = "block";
+    }
+    else
+    {
+      scriptIcon.style.display = "none";
+    }
+  }
+}
+catch (e) {}
+
+if (shouldAlert > 0)
+{
+  if (window.opener.g_classicAlerts.huntRoster == true)
+  {
+    var notify = window.opener.huntRosterNotify.value;
+    if (notify == "0")
+    {
+      var media = window.opener.huntRosterNotifyMedia.value;
+      if (media != "none") window.opener.playAlertMediaFile(media);
+    }
+    else if (notify == "1")
+    {
+      window.opener.speakAlertString(
+        window.opener.huntRosterNotifyWord.value
+      );
+    }
+  }
+
+  if (
+    g_rosterSettings.realtime == false &&
+      scriptExists &&
+      window.opener.g_crScript == 1
+  )
+  {
+    try
+    {
+      fs.writeFileSync(
+        dirPath + "cr-alert.json",
+        JSON.stringify(g_scriptReport, null, 2)
+      );
+
+      var thisProc = dirPath + script;
+      var cp = require("child_process");
+      var child = cp.spawn(thisProc, [], {
+        detached: true,
+        cwd: dirPath.slice(0, -1),
+        stdio: ["ignore", "ignore", "ignore"]
+      });
+      child.unref();
+    }
+    catch (e)
+    {
+      conosle.log(e);
+    }
+    g_scriptReport = Object();
+  }
+  else g_scriptReport = Object();
+}