// APPLICATION STATE
// =============================================================================
/** @type {Object} Sorting state tracking — initialized from user preference or factory default */
const _storedSortCol = localStorage.getItem('defaultSortColumn');
let sortColumn = _storedSortCol !== null ? parseInt(_storedSortCol, 10) : 4;
const _storedSortDir = localStorage.getItem('defaultSortDir');
let sortDirection = _storedSortDir || "asc";
/** @type {number|null} Index of item being edited (null = no edit in progress) */
let editingIndex = null;
/** @type {number|null} Index of item whose notes are being edited */
let notesIndex = null;
/** @type {number|null} Change log entry currently being edited */
let editingChangeLogIndex = null;
/** @type {number} Number of visible rows in the portal (scrollable table) view */
let itemsPerPage = Infinity;
/** @type {string} Current search query */
let searchQuery = "";
/** @type {Object} Chart instances for proper cleanup */
let chartInstances = {
typeChart: null,
locationChart: null,
};
/** @type {Object} Sparkline Chart.js instances keyed by metal (for cleanup) */
let sparklineInstances = {
silver: null,
gold: null,
platinum: null,
palladium: null,
};
/** @type {Set<string>} Available composition options */
let compositionOptions = new Set(["Gold", "Silver", "Platinum", "Palladium", "Alloy"]);
/** @type {Set<number>} Items currently showing market value instead of purchase price */
let marketValueViewItems = new Set();
/** @type {Object} Cached DOM elements for performance */
const elements = {
// Spot price elements
spotPriceDisplay: {},
spotSyncIcon: {},
spotRangeSelect: {},
spotSparkline: {},
// Form elements
inventoryForm: null,
inventoryTable: null,
itemMetal: null,
itemName: null,
itemQty: null,
itemType: null,
itemWeight: null,
itemWeightUnit: null,
itemGbDenom: null,
itemPrice: null,
itemMarketValue: null,
marketValueField: null,
dateField: null,
purchaseLocation: null,
storageLocation: null,
itemSerialNumber: null,
itemNotes: null,
itemDate: null,
itemSpotPrice: null,
itemCatalog: null,
itemYear: null,
itemGrade: null,
itemGradingAuthority: null,
itemCertNumber: null,
itemObverseImageUrl: null,
itemReverseImageUrl: null,
searchNumistaBtn: null,
lookupPcgsBtn: null,
spotLookupBtn: null,
itemPuritySelect: null,
itemPurity: null,
purityCustomWrapper: null,
// Spot price sync icons (per-metal)
syncIconSilver: null,
syncIconGold: null,
syncIconPlatinum: null,
syncIconPalladium: null,
// Import elements
importCsvFile: null,
importCsvOverride: null,
importCsvMerge: null,
importJsonFile: null,
importJsonOverride: null,
importJsonMerge: null,
importProgress: null,
importProgressText: null,
numistaImportFile: null,
numistaOverride: null,
numistaMerge: null,
// Export elements
exportCsvBtn: null,
exportJsonBtn: null,
exportPdfBtn: null,
cloudSyncBtn: null,
syncAllBtn: null,
// Vault encrypted backup elements
vaultExportBtn: null,
vaultImportBtn: null,
vaultImportFile: null,
vaultModal: null,
// Emergency reset button
removeInventoryDataBtn: null,
boatingAccidentBtn: null,
// Notes modal elements
notesModal: null,
notesTextarea: null,
saveNotesBtn: null,
cancelNotesBtn: null,
notesCloseBtn: null,
// Debug modal elements
debugModal: null,
debugCloseBtn: null,
// Details modal elements
detailsModal: null,
detailsModalTitle: null,
typeBreakdown: null,
locationBreakdown: null,
detailsCloseBtn: null,
totalTitles: null,
// Chart canvas elements
typeChart: null,
locationChart: null,
// Portal view elements
itemsPerPage: null,
itemCount: null,
// Change log elements
changeLogBtn: null,
backupReminder: null,
changeLogModal: null,
changeLogCloseBtn: null,
changeLogClearBtn: null,
changeLogTable: null,
storageUsage: null,
storageReportLink: null,
// Search elements
searchInput: null,
typeFilter: null,
metalFilter: null,
clearBtn: null,
newItemBtn: null,
searchResultsInfo: null,
activeFilters: null,
// Unified item modal elements (add/edit)
itemModal: null,
itemCloseBtn: null,
cancelItemBtn: null,
itemModalTitle: null,
itemModalSubmit: null,
itemSerial: null,
undoChangeBtn: null,
// About & acknowledgment modal elements
aboutBtn: null,
aboutModal: null,
ackModal: null,
ackAcceptBtn: null,
// Header toggle buttons (STACK-54)
headerThemeBtn: null,
headerCurrencyBtn: null,
// Layout section containers (STACK-54)
spotPricesSection: null,
totalsSectionEl: null,
searchSectionEl: null,
tableSectionEl: null,
// View item modal elements
viewItemModal: null,
viewModalCloseBtn: null,
// Settings modal elements
settingsBtn: null,
settingsModal: null,
// Sub-modals (stacking overlays opened from within Settings)
apiInfoModal: null,
apiHistoryModal: null,
goldbackHistoryModal: null,
cloudSyncModal: null,
apiQuotaModal: null,
// Spot price action buttons
spotSyncBtn: null,
spotAddBtn: null,
spotResetBtn: null,
// Totals display elements (organized by metal type)
totals: {
silver: {
items: null, // Total item count
weight: null, // Total weight in ounces
value: null, // Current market value
purchased: null, // Total purchase price
avgPrice: null, // Average price per ounce
avgPremium: null, // Average premium per ounce
premium: null, // Total premium paid
lossProfit: null, // Total loss/profit
},
gold: {
// Same structure as silver
items: null,
weight: null,
value: null,
purchased: null,
avgPrice: null,
avgPremium: null,
premium: null, // Total premium paid
lossProfit: null, // Total loss/profit
},
platinum: {
items: null,
weight: null,
value: null,
purchased: null,
avgPrice: null,
avgPremium: null,
premium: null, // Total premium paid
lossProfit: null, // Total loss/profit
},
palladium: {
items: null,
weight: null,
value: null,
purchased: null,
avgPrice: null,
avgPremium: null,
premium: null, // Total premium paid
lossProfit: null, // Total loss/profit
},
all: {
// Combined totals for all metals
items: null,
weight: null,
value: null,
purchased: null,
avgPrice: null,
avgPremium: null,
premium: null, // Total premium paid
lossProfit: null, // Total loss/profit
},
},
};
/** @type {Array} Change log entries */
let changeLog = JSON.parse(localStorage.getItem('changeLog') || '[]');
/** @type {Array} Main inventory data structure */
let inventory = [];
/** @type {Object} Current spot prices for all metals */
let spotPrices = {
silver: 0,
gold: 0,
platinum: 0,
palladium: 0,
};
/** @type {Array} Historical spot price records */
let spotHistory = [];
/** @type {Object} Per-item price history keyed by UUID (STACK-43) */
let itemPriceHistory = {};
/** @type {Object} Goldback denomination prices keyed by weight string (STACK-45) */
let goldbackPrices = {};
/** @type {Object} Goldback price history keyed by weight string (STACK-45) */
let goldbackPriceHistory = {};
/** @type {boolean} Whether Goldback denomination pricing is enabled (STACK-45) */
let goldbackEnabled = false;
/** @type {boolean} Whether Goldback real-time price estimation is enabled (STACK-52) */
let goldbackEstimateEnabled = false;
/** @type {number} User-configurable premium modifier for Goldback estimation (STACK-52) */
let goldbackEstimateModifier = GB_ESTIMATE_PREMIUM;
/** @type {string} User's selected display currency (ISO 4217), default USD (STACK-50) */
let displayCurrency = DEFAULT_CURRENCY;
/** @type {Object<string, number>} Cached exchange rates: 1 USD = rate × target currency (STACK-50) */
let exchangeRates = {};
/** @type {Object} Item tags mapping: { [uuid]: string[] } (STAK-126) */
let itemTags = {};
/** @type {Array} Catalog API call history records */
let catalogHistory = [];
/** @type {Object|null} Current Metals API configuration */
let apiConfig = null;
/** @type {Object|null} Cached API data with timestamp */
let apiCache = null;
/** @type {Object} Backward compatibility for catalogMap - now managed by catalogManager */
let catalogMap = new Proxy({}, {
get(target, prop) {
if (window.catalogManager) {
return catalogManager.getCatalogId(prop) || '';
}
return target[prop];
},
set(target, prop, value) {
if (window.catalogManager) {
catalogManager.setCatalogId(prop, value);
}
target[prop] = value;
return true;
},
deleteProperty(target, prop) {
if (window.catalogManager) {
catalogManager.setCatalogId(prop, '');
}
delete target[prop];
return true;
}
});
// =============================================================================