diff --git a/assets/css/main.css b/assets/css/main.css index 8162397..f68a4c9 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -15,6 +15,7 @@ @import url('./floating-elements.css'); @import url('./stats.css'); @import url('./comparison.css'); +@import url('./metadata.css'); /* Responsive design */ @import url('./responsive.css'); diff --git a/assets/css/metadata.css b/assets/css/metadata.css new file mode 100644 index 0000000..872fef6 --- /dev/null +++ b/assets/css/metadata.css @@ -0,0 +1,180 @@ +/* ======================================== + METADATA POPUP STYLES + ======================================== */ + +/* Metadata button on thumbnails */ +.metadata-btn { + position: absolute; + top: 8px; + right: 8px; + background: rgba(0, 0, 0, 0.7); + color: white; + border: none; + border-radius: 50%; + width: 32px; + height: 32px; + font-size: 16px; + cursor: pointer; + z-index: 10; + transition: all 0.2s ease; + backdrop-filter: blur(4px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + opacity: 0; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.grid-item:hover .metadata-btn { + opacity: 1; +} + +.metadata-btn:hover { + background: rgba(0, 0, 0, 0.9); + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.metadata-btn:active { + transform: scale(0.95); +} + +/* Grid item positioning for metadata button */ +.grid-item { + position: relative; +} + +/* Metadata popup */ +.metadata-popup { + background: var(--card-background); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + max-width: 320px; + min-width: 250px; + opacity: 0; + transform: translateY(-10px); + transition: all 0.2s ease; + backdrop-filter: blur(10px); + z-index: 1000; +} + +.metadata-popup.show { + opacity: 1; + transform: translateY(0); +} + +.metadata-popup-header { + padding: 12px 16px; + border-bottom: 1px solid var(--border-color); + background: var(--header-background); + border-radius: 8px 8px 0 0; +} + +.metadata-popup-header h4 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: var(--text-color); + word-break: break-word; +} + +.metadata-popup-content { + padding: 12px 16px; + max-height: 300px; + overflow-y: auto; +} + +.metadata-field { + display: flex; + margin-bottom: 8px; + gap: 8px; + align-items: flex-start; +} + +.metadata-field:last-child { + margin-bottom: 0; +} + +.metadata-key { + font-weight: 500; + color: var(--accent-color); + font-size: 12px; + min-width: 80px; + flex-shrink: 0; +} + +.metadata-value { + font-size: 12px; + color: var(--text-color); + word-break: break-word; + flex: 1; +} + +.metadata-value code { + background: var(--code-background); + padding: 2px 4px; + border-radius: 3px; + font-size: 11px; + font-family: 'Courier New', monospace; +} + +.metadata-tag { + background: var(--accent-color); + color: var(--background-color); + padding: 2px 6px; + border-radius: 12px; + font-size: 10px; + font-weight: 500; + margin-right: 4px; + display: inline-block; +} + +.metadata-more { + color: var(--breadcrumb-color); + font-style: italic; + font-size: 11px; +} + +.no-metadata { + color: var(--breadcrumb-color); + font-style: italic; + margin: 0; + text-align: center; + padding: 20px 0; +} + +/* Custom scrollbar for metadata popup */ +.metadata-popup-content::-webkit-scrollbar { + width: 6px; +} + +.metadata-popup-content::-webkit-scrollbar-track { + background: transparent; +} + +.metadata-popup-content::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 3px; +} + +.metadata-popup-content::-webkit-scrollbar-thumb:hover { + background: var(--accent-color); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .metadata-popup { + max-width: calc(100vw - 32px); + min-width: 200px; + } + + .metadata-btn { + width: 28px; + height: 28px; + font-size: 14px; + top: 6px; + right: 6px; + } +} diff --git a/assets/js/metadata-popup.js b/assets/js/metadata-popup.js new file mode 100644 index 0000000..085c3a9 --- /dev/null +++ b/assets/js/metadata-popup.js @@ -0,0 +1,212 @@ +/** + * Metadata Popup functionality for Gallery + * + * Handles showing metadata in small popups overlaid on plot thumbnails + */ + +class MetadataPopup { + constructor() { + this.activePopup = null; + this.init(); + } + + init() { + // Close popup when clicking outside + document.addEventListener('click', (e) => { + if (!e.target.closest('.metadata-btn') && !e.target.closest('.metadata-popup')) { + this.hidePopup(); + } + }); + + // Close popup on Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + this.hidePopup(); + } + }); + } + + showPopup(button, plotName, metadata) { + // Hide any existing popup + this.hidePopup(); + + // Create popup element + const popup = document.createElement('div'); + popup.className = 'metadata-popup'; + popup.innerHTML = this.formatMetadata(plotName, metadata); + + // Position popup relative to button + const rect = button.getBoundingClientRect(); + popup.style.position = 'fixed'; + popup.style.left = rect.left + 'px'; + popup.style.top = (rect.bottom + 5) + 'px'; + popup.style.zIndex = '1000'; + + // Add to DOM + document.body.appendChild(popup); + this.activePopup = popup; + + // Adjust position if popup goes off screen + setTimeout(() => { + const popupRect = popup.getBoundingClientRect(); + + // Adjust horizontal position + if (popupRect.right > window.innerWidth) { + popup.style.left = (rect.right - popupRect.width) + 'px'; + } + + // Adjust vertical position + if (popupRect.bottom > window.innerHeight) { + popup.style.top = (rect.top - popupRect.height - 5) + 'px'; + } + }, 0); + + // Animate in + requestAnimationFrame(() => { + popup.classList.add('show'); + }); + } + + hidePopup() { + if (this.activePopup) { + this.activePopup.classList.remove('show'); + setTimeout(() => { + if (this.activePopup && this.activePopup.parentNode) { + this.activePopup.parentNode.removeChild(this.activePopup); + } + this.activePopup = null; + }, 200); + } + } + + formatMetadata(plotName, metadata) { + if (!metadata || Object.keys(metadata).length === 0) { + return ` +
+ + `; + } + + let html = ` + + '; + return html; + } + + formatMetadataField(key, value) { + const displayKey = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + + let formattedValue; + if (value === null || value === undefined) { + formattedValue = 'null'; + } else if (typeof value === 'object') { + if (Array.isArray(value)) { + if (value.length <= 3) { + formattedValue = value.map(item => ``).join(' '); + } else { + formattedValue = `${value.slice(0, 3).map(item => ``).join(' ')} `; + } + } else { + // Show object as compact JSON for small objects, or just key count for large ones + const keys = Object.keys(value); + if (keys.length <= 3) { + formattedValue = '' + JSON.stringify(value) + '';
+ } else {
+ formattedValue = `Object with ${keys.length} properties`;
+ }
+ }
+ } else {
+ // Truncate long strings
+ const str = String(value);
+ formattedValue = str.length > 50 ? str.substring(0, 47) + '...' : str;
+ }
+
+ return `
+
+ `;
+ }
+}
+
+// Global instance
+window.metadataPopup = new MetadataPopup();
+
+// Global function for template usage
+window.showMetadataPopup = function(button, plotName, metadata) {
+ window.metadataPopup.showPopup(button, plotName, metadata);
+};
diff --git a/templates/gallery.html b/templates/gallery.html
index 227b1d5..a0ee24c 100644
--- a/templates/gallery.html
+++ b/templates/gallery.html
@@ -41,6 +41,9 @@