diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php
index 0fc55c061..24b5d34ba 100644
--- a/application/views/bandmap/list.php
+++ b/application/views/bandmap/list.php
@@ -33,10 +33,12 @@
DX Cluster - spot list
-
www.wavelog.org
-
+
www.wavelog.org
+
+
+
diff --git a/assets/css/bandmap_list.css b/assets/css/bandmap_list.css
index c4f89c574..ed6d8be18 100644
--- a/assets/css/bandmap_list.css
+++ b/assets/css/bandmap_list.css
@@ -153,6 +153,78 @@ tbody a {
transition: all 0.2s ease;
}
+/* CAT Control - Locked Sorting */
+.spottable.cat-sorting-locked thead th {
+ cursor: not-allowed !important;
+ pointer-events: none;
+ opacity: 0.6;
+}
+
+.spottable.cat-sorting-locked thead th:hover {
+ background-color: transparent !important;
+}
+
+.spottable.cat-sorting-locked thead th.sorting_asc::after,
+.spottable.cat-sorting-locked thead th.sorting_desc::after {
+ opacity: 1 !important;
+}
+
+/* CAT Control - Frequency Gradient */
+.cat-frequency-gradient {
+ transition: background-color 0.3s ease, var(--bs-table-bg) 0.3s ease !important;
+}
+
+/* Force gradient background to override Bootstrap striping AND spot lifecycle colors - MAXIMUM SPECIFICITY */
+/* Override normal rows */
+table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient,
+table.dataTable.table-striped tbody tr.cat-frequency-gradient,
+table.table-striped tbody tr.cat-frequency-gradient,
+.table-striped > tbody > tr.cat-frequency-gradient,
+table tbody tr.cat-frequency-gradient {
+ background-color: var(--bs-table-bg) !important;
+}
+
+/* Override expiring spots (red) */
+table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient.spot-expiring,
+table.dataTable.table-striped tbody tr.cat-frequency-gradient.spot-expiring,
+table tbody tr.cat-frequency-gradient.spot-expiring {
+ background-color: var(--bs-table-bg) !important;
+}
+
+/* Override very new spots (green) */
+table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient.spot-very-new,
+table.dataTable.table-striped tbody tr.cat-frequency-gradient.spot-very-new,
+table tbody tr.cat-frequency-gradient.spot-very-new {
+ background-color: var(--bs-table-bg) !important;
+}
+
+/* Override fresh spots - force gradient color over fresh blue */
+table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient.fresh,
+table.dataTable.table-striped tbody tr.cat-frequency-gradient.fresh,
+table tbody tr.cat-frequency-gradient.fresh {
+ /* Override both the variable and background directly */
+ background-color: var(--bs-table-bg) !important;
+ /* Prevent .fresh class from setting its own --bs-table-bg */
+ transition: background-color 0.3s ease !important;
+}
+
+/* Override even and odd striping */
+table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient:nth-of-type(odd),
+table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient:nth-of-type(even),
+table.dataTable.table-striped tbody tr.cat-frequency-gradient:nth-of-type(odd),
+table.dataTable.table-striped tbody tr.cat-frequency-gradient:nth-of-type(even),
+table.table-striped tbody tr.cat-frequency-gradient:nth-of-type(odd),
+table.table-striped tbody tr.cat-frequency-gradient:nth-of-type(even),
+.table-striped > tbody > tr.cat-frequency-gradient:nth-of-type(odd),
+.table-striped > tbody > tr.cat-frequency-gradient:nth-of-type(even) {
+ --bs-table-accent-bg: transparent !important;
+ --bs-table-striped-bg: transparent !important;
+}
+
+.cat-frequency-gradient:hover {
+ filter: brightness(0.95);
+}
+
/* Status bar styling */
.status-bar {
font-size: 0.875rem;
@@ -301,6 +373,55 @@ tbody a {
vertical-align: middle;
}
+/* Fullscreen toggle button wrapper - large clickable area */
+#fullscreenToggleWrapper {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ z-index: 10;
+ border-radius: 0.25rem;
+ transition: background-color 150ms ease;
+}
+
+#fullscreenToggleWrapper:hover {
+ background-color: rgba(0, 0, 0, 0.08) !important;
+}
+
+/* Fullscreen toggle button - ensure proper clickable area */
+#fullscreenToggle {
+ min-width: 48px;
+ min-height: 48px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer !important;
+ position: relative;
+ flex-shrink: 0;
+}
+
+#fullscreenToggle:hover {
+ background-color: transparent !important;
+}
+
+#fullscreenIcon {
+ pointer-events: none; /* Icon shouldn't intercept clicks */
+ font-size: 1.4rem !important; /* Make icon slightly bigger too */
+}
+
+/* Ensure wavelog text doesn't overlap button */
+.fullscreen-wavelog-text {
+ pointer-events: auto;
+ flex-shrink: 1;
+ white-space: nowrap;
+ cursor: pointer;
+}
+
+.fullscreen-wavelog-text:hover {
+ text-decoration: underline !important;
+ opacity: 0.8;
+}
+
/* Fullscreen mode - CLEAN REBUILD */
.bandmap-logo-fullscreen {
display: none;
diff --git a/assets/js/sections/bandmap_list.js b/assets/js/sections/bandmap_list.js
index 72c2b09a0..1e230565f 100644
--- a/assets/js/sections/bandmap_list.js
+++ b/assets/js/sections/bandmap_list.js
@@ -384,16 +384,17 @@ $(function() {
'orderable': false
}
],
- search: { smart: true },
- drawCallback: function(settings) {
- // Update status bar after table is drawn (including after search)
- let totalRows = cachedSpotData ? cachedSpotData.length : 0;
- let displayedRows = this.api().rows({ search: 'applied' }).count();
- updateStatusBar(totalRows, displayedRows, getServerFilterText(), getClientFilterText(), false, false);
- }
- });
+ search: { smart: true },
+ drawCallback: function(settings) {
+ // Update status bar after table is drawn (including after search)
+ let totalRows = cachedSpotData ? cachedSpotData.length : 0;
+ let displayedRows = this.api().rows({ search: 'applied' }).count();
+ updateStatusBar(totalRows, displayedRows, getServerFilterText(), getClientFilterText(), false, false);
- $('.spottable tbody').off('click', 'tr').on('click', 'tr', function(e) {
+ // Note: CAT frequency gradient is now updated only from updateCATui (every 3s)
+ // to prevent recursion issues with table redraws
+ }
+ }); $('.spottable tbody').off('click', 'tr').on('click', 'tr', function(e) {
// Don't trigger row click if clicking on a link (LoTW, POTA, SOTA, WWFF, QRZ, etc.)
if ($(e.target).is('a') || $(e.target).closest('a').length) {
return;
@@ -1039,18 +1040,36 @@ $(function() {
}
// Add row with appropriate class
+ let addedRow = table.rows.add(data).draw().nodes().to$();
+
if (rowClass) {
- let addedRow = table.rows.add(data).draw().nodes().to$();
addedRow.addClass(rowClass);
if (ttl === 0) {
console.log('Added expiring class to row:', addedRow.hasClass('spot-expiring'));
}
- } else {
- table.rows.add(data).draw();
+ }
+
+ // Apply CAT frequency gradient AFTER adding lifecycle classes to ensure it overrides
+ if (isCatTrackingEnabled && currentRadioFrequency) {
+ const spotFreqKhz = single.frequency * 1000; // Convert MHz to kHz
+ const gradientColor = getFrequencyGradientColor(spotFreqKhz, currentRadioFrequency);
+ if (gradientColor) {
+ // Store gradient color and frequency for later reapplication
+ addedRow.attr('data-spot-frequency', spotFreqKhz);
+ addedRow.attr('data-gradient-color', gradientColor);
+ // Use setProperty with priority 'important' to force override
+ addedRow.each(function() {
+ this.style.setProperty('--bs-table-bg', gradientColor, 'important');
+ this.style.setProperty('--bs-table-accent-bg', gradientColor, 'important');
+ this.style.setProperty('background-color', gradientColor, 'important');
+ });
+ addedRow.addClass('cat-frequency-gradient');
+ }
}
});
// Remove "fresh" highlight after 10 seconds
+ // (CAT gradient is updated every 3s from updateCATui, no need to force here)
setTimeout(function () {
$(".fresh").removeClass("fresh");
}, 10000);
@@ -1532,23 +1551,7 @@ $(function() {
startRefreshTimer();
});
} // Highlight rows within ±20 kHz of specified frequency (for CAT integration)
- function highlight_current_qrg(qrg) {
- var table = get_dtable();
- table.rows().eq(0).each( function ( index ) {
- let row = table.row( index );
- var d=row.data();
- var distance=Math.abs(parseInt(d[1])-qrg);
- if (distance<=20) {
- distance++;
- alpha=(.5/distance);
- $(row.node()).css('--bs-table-bg', 'rgba(0,0,255,' + alpha + ')');
- $(row.node()).css('--bs-table-accent-bg', 'rgba(0,0,255,' + alpha + ')');
- } else {
- $(row.node()).css('--bs-table-bg', '');
- $(row.node()).css('--bs-table-accent-bg', '');
- }
- });
- }
+ // Old highlight_current_qrg function removed - now using updateFrequencyGradientColors
// Initialize DataTable
var table=get_dtable();
@@ -2022,8 +2025,109 @@ $(function() {
var isCatTrackingEnabled = false; // Track CAT Control button state
window.isCatTrackingEnabled = isCatTrackingEnabled; // Expose to window for cat.js
+ var currentRadioFrequency = null; // Store current radio frequency in kHz
+ var lastGradientFrequency = null; // Track last frequency used for gradient update
- // Save reference to cat.js's updateCATui if it exists
+ /**
+ * Calculate frequency gradient color based on distance from radio frequency
+ * @param {number} spotFreqKhz - Spot frequency in kHz
+ * @param {number} radioFreqKhz - Radio frequency in kHz
+ * @returns {string|null} - CSS background color or null if outside gradient range
+ */
+ function getFrequencyGradientColor(spotFreqKhz, radioFreqKhz) {
+ if (!radioFreqKhz || !isCatTrackingEnabled) {
+ return null;
+ }
+
+ // Determine if we're in LSB or USB mode (below/above 10 MHz)
+ const isLSB = radioFreqKhz < 10000;
+
+ // Calculate frequency difference in kHz
+ // For LSB: lower frequencies are "closer" to tune (radio freq - spot freq)
+ // For USB: higher frequencies are "closer" to tune (spot freq - radio freq)
+ let freqDiff;
+ if (isLSB) {
+ freqDiff = Math.abs(radioFreqKhz - spotFreqKhz);
+ } else {
+ freqDiff = Math.abs(spotFreqKhz - radioFreqKhz);
+ }
+
+ // Maximum gradient range: 2.5 kHz
+ const maxGradientKhz = 2.5;
+
+ if (freqDiff > maxGradientKhz) {
+ return null; // Outside gradient range, use default color
+ }
+
+ // Calculate gradient factor: 0 (perfectly tuned) to 1 (2.5 kHz away)
+ const gradientFactor = freqDiff / maxGradientKhz;
+
+ // Violet color for perfectly tuned: rgb(138, 43, 226) - BlueViolet
+ // Fade to transparent as we move away
+ const alpha = 1 - gradientFactor; // 1 at perfect tune, 0 at 2.5 kHz
+ const intensity = 0.3 + (alpha * 0.4); // Range from 0.3 to 0.7 alpha
+
+ return `rgba(138, 43, 226, ${intensity})`;
+ }
+
+ /**
+ * Update frequency gradient colors for all visible table rows
+ * Called when radio frequency changes
+ */
+ function updateFrequencyGradientColors(forceUpdate = false) {
+ if (!isCatTrackingEnabled || !currentRadioFrequency) {
+ return;
+ }
+
+ // Skip update if frequency hasn't changed significantly (unless forced)
+ // Only update if frequency changed by more than 500 Hz to reduce flickering
+ if (!forceUpdate && lastGradientFrequency !== null) {
+ const freqDiff = Math.abs(currentRadioFrequency - lastGradientFrequency);
+ if (freqDiff < 0.5) { // 500 Hz threshold
+ return;
+ }
+ }
+
+ lastGradientFrequency = currentRadioFrequency;
+ var table = get_dtable();
+ let coloredCount = 0;
+
+ // Iterate through all visible rows
+ table.rows({ search: 'applied' }).every(function() {
+ const row = this.node();
+ const rowData = this.data();
+
+ if (!rowData || !rowData[2]) return;
+
+ // Get spot frequency (column 2, in MHz)
+ const spotFreqMhz = parseFloat(rowData[2]);
+ const spotFreqKhz = spotFreqMhz * 1000;
+
+ // Calculate gradient color
+ const gradientColor = getFrequencyGradientColor(spotFreqKhz, currentRadioFrequency);
+
+ // Store gradient data for persistence
+ $(row).attr('data-spot-frequency', spotFreqKhz);
+
+ if (gradientColor) {
+ coloredCount++;
+ // Store and apply gradient color directly to override Bootstrap striping
+ $(row).attr('data-gradient-color', gradientColor);
+ // Use setProperty with 'important' priority to force override .fresh, .spot-expiring, etc.
+ row.style.setProperty('--bs-table-bg', gradientColor, 'important');
+ row.style.setProperty('--bs-table-accent-bg', gradientColor, 'important');
+ row.style.setProperty('background-color', gradientColor, 'important');
+ $(row).addClass('cat-frequency-gradient');
+ } else {
+ // Remove gradient styling if outside range
+ $(row).removeAttr('data-gradient-color');
+ $(row).removeClass('cat-frequency-gradient');
+ row.style.removeProperty('--bs-table-bg');
+ row.style.removeProperty('--bs-table-accent-bg');
+ row.style.removeProperty('background-color');
+ }
+ });
+} // Save reference to cat.js's updateCATui if it exists
var catJsUpdateCATui = window.updateCATui;
// Override updateCATui to add bandmap-specific behavior
@@ -2034,6 +2138,9 @@ $(function() {
console.log('Bandmap CAT Update - Frequency:', data.frequency, 'Band:', band, 'Control enabled:', isCatTrackingEnabled);
+ // Store current radio frequency (convert Hz to kHz)
+ currentRadioFrequency = data.frequency / 1000;
+
// Bandmap-specific: Update band filter if CAT Control is enabled
if (isCatTrackingEnabled) {
const currentBands = $("#band").val() || [];
@@ -2067,12 +2174,10 @@ $(function() {
}
}
}
- }
- // Bandmap-specific: Highlight current QRG in the spot list
- if (data.frequency) {
- highlight_current_qrg((parseInt(data.frequency))/1000);
- }
+ // Update frequency gradient colors for all visible rows
+ updateFrequencyGradientColors();
+ }
// Call cat.js's original updateCATui for standard CAT UI updates
if (typeof catJsUpdateCATui === 'function') {
@@ -2098,7 +2203,13 @@ $(function() {
let isFullscreen = false;
- $('#fullscreenToggle').on('click', function() {
+ // Handle clicks on both the button and wrapper
+ $('#fullscreenToggle, #fullscreenToggleWrapper').on('click', function(e) {
+ // Prevent double firing if clicking directly on button
+ if (e.target.id === 'fullscreenToggle' && this.id === 'fullscreenToggleWrapper') {
+ return;
+ }
+
const container = $('#bandmapContainer');
const icon = $('#fullscreenIcon');
@@ -2106,7 +2217,7 @@ $(function() {
container.addClass('bandmap-fullscreen');
$('body').addClass('fullscreen-active');
icon.removeClass('fa-expand').addClass('fa-compress');
- $(this).attr('title', 'Exit Fullscreen');
+ $('#fullscreenToggle').attr('title', 'Exit Fullscreen');
isFullscreen = true;
@@ -3001,6 +3112,87 @@ $(function() {
$('.cat-control-info').remove();
}
+ // ========================================
+ // CAT CONTROL - TABLE SORTING LOCK
+ // ========================================
+
+ /**
+ * Lock table sorting to frequency column only (descending) when CAT Control is active
+ */
+ function lockTableSortingToFrequency() {
+ var table = get_dtable();
+
+ // Add class to table for CSS styling
+ $('.spottable').addClass('cat-sorting-locked');
+
+ // Force sort by frequency (column 2) descending
+ table.order([2, 'desc']).draw();
+
+ // Disable sorting on all columns
+ table.settings()[0].aoColumns.forEach(function(col, index) {
+ col.bSortable = false;
+ });
+
+ // Disable click events on all column headers
+ $('.spottable thead th').off('click.DT');
+
+ // Redraw column headers to update sort icons
+ table.columns.adjust();
+
+ console.log('Table sorting locked to Frequency (DESC) only');
+ }
+
+ /**
+ * Unlock table sorting when CAT Control is disabled
+ */
+ function unlockTableSorting() {
+ var table = get_dtable();
+
+ // Remove class from table
+ $('.spottable').removeClass('cat-sorting-locked');
+
+ // Re-enable sorting on all columns that were originally sortable
+ // Based on columnDefs: columns 5, 6, 7, 11, 12, 13, 14 are not sortable
+ const nonSortableColumns = [5, 6, 7, 11, 12, 13, 14];
+
+ table.settings()[0].aoColumns.forEach(function(col, index) {
+ if (!nonSortableColumns.includes(index)) {
+ col.bSortable = true;
+ }
+ });
+
+ // Re-enable DataTables default click handlers
+ table.off('click.DT', 'thead th');
+
+ // Reset to default sort (frequency ascending)
+ table.order([2, 'asc']).draw();
+
+ // Redraw column headers to update sort icons
+ table.columns.adjust();
+
+ // Clear frequency gradient colors
+ clearFrequencyGradientColors();
+
+ console.log('Table sorting unlocked');
+ }
+
+ /**
+ * Clear all frequency gradient colors from table rows
+ */
+ function clearFrequencyGradientColors() {
+ var table = get_dtable();
+
+ table.rows().every(function() {
+ const row = this.node();
+ $(row).removeClass('cat-frequency-gradient');
+ $(row).css({
+ '--bs-table-bg': '',
+ '--bs-table-accent-bg': '',
+ 'background-color': ''
+ });
+ });
+ }
+
// Toggle CAT Control
$('#toggleCatTracking').on('click', function() {
let btn = $(this);
@@ -3017,6 +3209,9 @@ $(function() {
// Re-enable band filter controls
enableBandFilterControls();
+
+ // Unlock table sorting
+ unlockTableSorting();
} else {
// Enable CAT Control
btn.removeClass('btn-secondary').addClass('btn-success');
@@ -3034,6 +3229,9 @@ $(function() {
// Disable band filter controls
disableBandFilterControls();
+ // Lock table sorting to frequency only
+ lockTableSortingToFrequency();
+
// Immediately apply current radio frequency if available
if (window.lastCATData && window.lastCATData.frequency) {
console.log('Applying current radio frequency:', window.lastCATData.frequency);