---
// src/components/KnowledgeGraph.astro
// Enhanced interactive visualization of content connections using Cytoscape.js

export interface GraphNode {
  id: string;
  label: string;
  type: 'post' | 'tag' | 'category'; // Node types to distinguish posts from tags
  category?: string;
  tags?: string[];
  url?: string; // URL for linking
}

export interface GraphEdge {
  source: string;
  target: string;
  type: 'post-tag' | 'post-category' | 'post-post'; // Edge types
  strength?: number;
}

export interface GraphData {
  nodes: GraphNode[];
  edges: GraphEdge[];
}

interface Props {
  graphData: GraphData;
  height?: string; // e.g., '500px'
  initialFilter?: string; // Optional initial filter
}

const { graphData, height = "50vh", initialFilter = "all" } = Astro.props;

// Generate colors based on node types
const nodeTypeColors = {
  'post': '#3B82F6', // Blue for posts
  'tag': '#10B981',  // Green for tags
  'category': '#8B5CF6' // Purple for categories
};

// Generate predefined colors for categories
const predefinedColors = { 
  'Kubernetes': '#326CE5', 'Docker': '#2496ED', 'DevOps': '#FF6F61',
  'Homelab': '#06B6D4', 'Networking': '#9333EA', 'Infrastructure': '#10B981',
  'Automation': '#F59E0B', 'Security': '#EF4444', 'Monitoring': '#6366F1',
  'Storage': '#8B5CF6', 'Obsidian': '#7C3AED', 'Tutorial': '#3B82F6',
  'Uncategorized': '#A0AEC0'
};

// Calculate node sizes
const nodeSizes = {};
const minSize = 15; const maxSize = 35;
const degreeMap = new Map();
graphData.nodes.forEach(node => degreeMap.set(node.id, 0));
graphData.edges.forEach(edge => {
  degreeMap.set(edge.source, (degreeMap.get(edge.source) || 0) + 1);
  degreeMap.set(edge.target, (degreeMap.get(edge.target) || 0) + 1);
});
const maxDegree = Math.max(...Array.from(degreeMap.values()), 1);
graphData.nodes.forEach(node => {
  const degree = degreeMap.get(node.id) || 0;
  // Make tags slightly smaller than posts by default
  const baseSize = node.type === 'post' ? minSize : minSize * 0.8;
  const normalizedSize = maxDegree === 0 ? 0.5 : degree / maxDegree;
  nodeSizes[node.id] = baseSize + normalizedSize * (maxSize - minSize);
});

// Count node types for legend
const nodeTypeCounts = {
  post: graphData.nodes.filter(node => node.type === 'post').length,
  tag: graphData.nodes.filter(node => node.type === 'tag').length,
  category: graphData.nodes.filter(node => node.type === 'category').length
};
---

<!-- Include Cytoscape via CDN with is:inline to ensure it loads before the script runs -->
<script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js" is:inline></script>

<div class="graph-container-wrapper" style={`--graph-height: ${height};`}>
  <!-- Graph Instructions -->
  <div class="graph-instructions">
    <details class="instructions-details">
      <summary class="instructions-summary">How to use the Knowledge Graph</summary>
      <div class="instructions-content">
        <p>This Knowledge Graph visualizes connections between blog content:</p>
        <ul>
          <li><span class="node-example post-node"></span> <strong>Posts</strong> - Blog articles (circle nodes)</li>
          <li><span class="node-example tag-node"></span> <strong>Tags</strong> - Content topics (diamond nodes)</li>
        </ul>
        <p>Interactions:</p>
        <ul>
          <li>Click a node to see its connections and details</li>
          <li>Click a tag node to filter posts by that tag</li>
          <li>Click a post node to highlight that specific post</li>
          <li>Use the filter buttons below to focus on specific topics</li>
          <li>Use mouse wheel to zoom in/out and drag to pan</li>
          <li>Click an empty area to reset the view</li>
        </ul>
      </div>
    </details>
  </div>

  <!-- Loading Animation -->
  <div id="graph-loading" class="graph-loading">
    <div class="loading-spinner">
      <div class="spinner-ring"></div>
      <div class="spinner-ring"></div>
      <div class="spinner-ring"></div>
    </div>
    <div class="loading-text">Initializing Knowledge Graph...</div>
  </div>

  <!-- Cytoscape Container -->
  <div id="knowledge-graph" class="graph-container"></div>

  <!-- Node Details Panel -->
  <div id="node-details" class="node-details">
    <div class="node-details-header">
      <h3 id="node-title" class="node-title">Node Title</h3>
      <button id="close-details" class="close-button" aria-label="Close details">
        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
      </button>
    </div>
    <div id="node-type" class="node-type">
      <span class="type-value">Type</span>
    </div>
    <div id="node-category" class="node-category">
      <span class="category-label">Category:</span>
      <span class="category-value">Category Name</span>
    </div>
    <div id="node-tags" class="node-tags">
      <span class="tags-label">Tags:</span>
      <div class="tags-container">
        <!-- Tags populated by JS -->
      </div>
    </div>
    <div id="node-connections" class="node-connections">
      <span class="connections-label">Connections:</span>
      <ul class="connections-list">
        <!-- Connections populated by JS -->
      </ul>
    </div>
    <a href="#" id="node-link" class="node-link" target="_self" rel="noopener noreferrer">View Content</a>
  </div>

  <!-- Graph Controls -->
  <div class="graph-controls">
     <div class="graph-filters">
       <button class="graph-filter active" data-filter="all" style="--filter-color: var(--accent-primary);">All</button>
       <button class="graph-filter" data-filter="posts" style="--filter-color: #3B82F6;">Posts ({nodeTypeCounts.post})</button>
       <button class="graph-filter" data-filter="tags" style="--filter-color: #10B981;">Tags ({nodeTypeCounts.tag})</button>
     </div>
     <div class="graph-actions">
        <button id="zoom-in" class="graph-action" aria-label="Zoom In">
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
        </button>
        <button id="zoom-out" class="graph-action" aria-label="Zoom Out">
           <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>
        </button>
        <button id="reset-graph" class="graph-action" aria-label="Reset View">
           <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2v6h6"></path><path d="M21 12A9 9 0 0 0 6 5.3L3 8"></path><path d="M21 22v-6h-6"></path><path d="M3 12a9 9 0 0 0 15 6.7l3-2.7"></path></svg>
        </button>
     </div>
  </div>
</div>

<script define:vars={{ graphData, nodeTypeColors, nodeSizes, predefinedColors, initialFilter }}>
  // Initialize the graph when the DOM is ready
  function initializeGraph() {
    // Check if Cytoscape is loaded
    if (typeof cytoscape === 'undefined') {
      console.error("Cytoscape library not loaded. Make sure it's included via the script tag.");
      const loadingEl = document.getElementById('graph-loading');
      if(loadingEl) loadingEl.innerHTML = "<p>Error: Cytoscape library not loaded.</p>";
      return; 
    }

    const loadingEl = document.getElementById('graph-loading');
    const nodeDetailsEl = document.getElementById('node-details');
    const closeDetailsBtn = document.getElementById('close-details');
    const graphContainer = document.getElementById('knowledge-graph');

    if (!graphContainer) {
        console.error("Knowledge graph container not found!");
        return;
    }
    if (!graphData || !graphData.nodes) {
        console.error("Graph data is missing or invalid.");
        if(loadingEl) loadingEl.innerHTML = "<p>Error loading graph data.</p>";
        return;
    }

    // Format data for Cytoscape
    const elements = [];
    
    // Add nodes with appropriate styling based on type
    graphData.nodes.forEach(node => {
      let nodeColor;
      
      if (node.type === 'post') {
        // Posts get category color if available
        nodeColor = node.category && predefinedColors[node.category] 
          ? predefinedColors[node.category] 
          : nodeTypeColors['post'];
      } else {
        // Tags and categories get their type color
        nodeColor = nodeTypeColors[node.type];
      }
      
      elements.push({
        data: {
          id: node.id,
          label: node.label,
          type: node.type,
          category: node.category || '',
          tags: node.tags || [],
          size: nodeSizes[node.id] || 25, 
          color: nodeColor, 
          url: node.url || '#'
        }
      });
    });
    
    // Add edges
    graphData.edges.forEach((edge, index) => {
      if (graphData.nodes.some(n => n.id === edge.source) && graphData.nodes.some(n => n.id === edge.target)) {
          elements.push({
            data: { 
              id: `e${index}`, 
              source: edge.source, 
              target: edge.target, 
              type: edge.type || 'post-tag',
              weight: edge.strength || 1 
            }
          });
      } else {
          console.warn(`Skipping edge e${index} due to missing node: ${edge.source} -> ${edge.target}`);
      }
    });

    // Initialize Cytoscape
    const cy = cytoscape({
      container: graphContainer,
      elements: elements,
      style: [ 
         // Base node styles
         { selector: 'node', style: { 
             'background-color': 'data(color)', 
             'label': 'data(label)', 
             'width': 'data(size)', 
             'height': 'data(size)', 
             'font-size': '10px', 
             'color': '#E2E8F0', 
             'text-valign': 'bottom', 
             'text-halign': 'center', 
             'text-margin-y': '7px', 
             'text-background-opacity': 0.7, 
             'text-background-color': '#0F1219', 
             'text-background-padding': '3px', 
             'text-background-shape': 'roundrectangle', 
             'text-max-width': '120px', 
             'text-wrap': 'ellipsis', 
             'text-overflow-wrap': 'anywhere', 
             'border-width': '2px', 
             'border-color': '#0F1219', 
             'border-opacity': 0.8, 
             'z-index': 10,
             'text-outline-width': 1, 
             'text-outline-color': '#000', 
             'text-outline-opacity': 0.5
         }},
         // Post node specific styles
         { selector: 'node[type="post"]', style: {
             'shape': 'ellipse',
             'border-width': '2px'
         }},
         // Tag node specific styles
         { selector: 'node[type="tag"]', style: {
             'shape': 'diamond',
             'border-width': '1px',
             'border-color': '#10B981',
             'border-opacity': 0.9
         }},
         // Category node specific styles
         { selector: 'node[type="category"]', style: {
             'shape': 'hexagon',
             'border-width': '1px'
         }},
         // Edge styles
         { selector: 'edge', style: { 
             'width': 'mapData(weight, 1, 10, 1, 3)', 
             'line-color': 'rgba(226, 232, 240, 0.2)', 
             'curve-style': 'bezier', 
             'opacity': 0.6, 
             'z-index': 1 
         }},
         // Post-tag edge specific styles
         { selector: 'edge[type="post-tag"]', style: {
             'line-color': 'rgba(16, 185, 129, 0.4)',
             'line-style': 'dashed'
         }},
         // Post-post edge specific styles
         { selector: 'edge[type="post-post"]', style: {
             'line-color': 'rgba(59, 130, 246, 0.4)',
             'line-style': 'solid'
         }},
         // Selection and hover styles
         { selector: '.highlighted', style: { 'background-color': 'data(color)', 'border-color': '#FFFFFF', 'border-width': '3px', 'color': '#FFFFFF', 'text-background-opacity': 0.9, 'opacity': 1, 'z-index': 20 } },
         { selector: '.filtered', style: { 'background-color': 'data(color)', 'border-color': '#FFFFFF', 'border-width': '2px', 'color': '#FFFFFF', 'text-background-opacity': 0.8, 'opacity': 0.8, 'z-index': 15 } },
         { selector: '.faded', style: { 'opacity': 0.15, 'text-opacity': 0.3, 'background-opacity': 0.3, 'z-index': 1 } }, 
         { selector: 'node:selected', style: { 'border-width': '4px', 'border-color': '#FFFFFF', 'border-opacity': 1, 'background-color': 'data(color)', 'text-opacity': 1, 'color': '#FFFFFF', 'z-index': 30 } },
         { selector: 'edge:selected', style: { 'width': 'mapData(weight, 1, 10, 2, 6)', 'line-color': '#FFFFFF', 'opacity': 1, 'z-index': 30 } }
      ],
      // Update layout for better visualization of post-tag connections
      layout: { 
        name: 'cose', 
        idealEdgeLength: 80, 
        nodeOverlap: 20, 
        refresh: 20, 
        fit: true, 
        padding: 30, 
        randomize: false, 
        componentSpacing: 100, 
        nodeRepulsion: 450000, 
        edgeElasticity: 100, 
        nestingFactor: 5, 
        gravity: 80, 
        numIter: 1000, 
        initialTemp: 200, 
        coolingFactor: 0.95, 
        minTemp: 1.0 
      },
      zoom: 1, minZoom: 0.1, maxZoom: 3, zoomingEnabled: true, userZoomingEnabled: true, panningEnabled: true, userPanningEnabled: true, boxSelectionEnabled: false,
    });

    // Hide loading screen
    if (loadingEl) {
      setTimeout(() => { loadingEl.classList.add('hidden'); }, 500); 
    }

    // --- Interactions ---
    let hoverTimeout;
    cy.on('mouseover', 'node', function(e) {
        const node = e.target;
        clearTimeout(hoverTimeout); 
        node.addClass('highlighted');
        node.connectedEdges().addClass('highlighted');
        graphContainer.style.cursor = 'pointer'; 
    });

    cy.on('mouseout', 'node', function(e) {
        const node = e.target;
        hoverTimeout = setTimeout(() => {
            if (!node.selected()) {
                node.removeClass('highlighted');
                node.connectedEdges().removeClass('highlighted');
            }
        }, 100);
        graphContainer.style.cursor = 'default'; 
    });

    cy.on('tap', 'node', function(e) {
      const node = e.target;
      const nodeData = node.data();
      
      if (nodeDetailsEl) {
          // Set node title
          document.getElementById('node-title').textContent = nodeData.label;
          
          // Set node type
          const nodeTypeEl = document.getElementById('node-type').querySelector('.type-value') || 
                             document.getElementById('node-type');
          nodeTypeEl.textContent = nodeData.type.charAt(0).toUpperCase() + nodeData.type.slice(1);
          nodeTypeEl.className = `type-value ${nodeData.type}-type`;
          
          // Set category if it's a post
          const categorySection = document.getElementById('node-category');
          if (nodeData.type === 'post' && nodeData.category) {
              categorySection.style.display = 'block';
              const categoryEl = categorySection.querySelector('.category-value');
              categoryEl.textContent = nodeData.category;
              // Use category colors
              const catColor = predefinedColors[nodeData.category] || 'var(--text-secondary)'; 
              categoryEl.style.backgroundColor = `${catColor}33`; // Add alpha
              categoryEl.style.color = catColor;
          } else {
              categorySection.style.display = 'none';
          }
          
          // Set tags if it's a post
          const tagsSection = document.getElementById('node-tags');
          const tagsContainer = tagsSection.querySelector('.tags-container');
          
          if (nodeData.type === 'post' && nodeData.tags && nodeData.tags.length > 0) {
              tagsSection.style.display = 'block';
              tagsContainer.innerHTML = '';
              nodeData.tags.forEach(tag => {
                const tagEl = document.createElement('span');
                tagEl.className = 'tag';
                tagEl.textContent = tag;
                tagEl.addEventListener('click', () => {
                  // Find the tag node and trigger its selection
                  const tagNode = cy.getElementById(`tag-${tag}`);
                  if (tagNode.length > 0) {
                    tagNode.trigger('tap');
                  }
                  
                  // Try to find and click tag filter button
                  try {
                    const tagFilterBtn = Array.from(
                      document.querySelectorAll('.tag-filter-btn')
                    ).find(btn => btn.dataset.tag === tag);
                    
                    if (tagFilterBtn) {
                      tagFilterBtn.click();
                    }
                  } catch (e) {
                    console.log('Tag filter button not found');
                  }
                });
                tagsContainer.appendChild(tagEl);
              });
          } else {
              tagsSection.style.display = 'none';
          }
          
          // Set connections
          const connectionsList = document.getElementById('node-connections').querySelector('.connections-list');
          connectionsList.innerHTML = '';
          const connectedNodes = node.neighborhood('node');
          if (connectedNodes.length > 0) {
            connectedNodes.forEach(connectedNode => {
              const connectedData = connectedNode.data();
              const listItem = document.createElement('li');
              const link = document.createElement('a');
              link.href = '#'; 
              link.textContent = connectedData.label;
              link.dataset.id = connectedData.id;
              link.addEventListener('click', (evt) => {
                evt.preventDefault();
                cy.$(':selected').unselect(); 
                const targetNode = cy.getElementById(connectedData.id);
                if (targetNode) {
                    targetNode.select(); 
                    cy.animate({ center: { eles: targetNode }, zoom: cy.zoom() }, { duration: 300 }); 
                    targetNode.trigger('tap'); 
                }
              });
              listItem.appendChild(link);
              connectionsList.appendChild(listItem);
            });
          } else {
            connectionsList.innerHTML = '<li>No connections</li>';
          }
          
          // Set link text and URL based on node type
          const nodeLink = document.getElementById('node-link');
          if (nodeData.type === 'post') {
            nodeLink.textContent = 'Read Article';
          } else if (nodeData.type === 'tag') {
            nodeLink.textContent = 'Browse Tag';
          } else {
            nodeLink.textContent = 'View Content';
          }
          nodeLink.href = nodeData.url;
          
          nodeDetailsEl.classList.add('active');
      }
      
      // Highlight the node and its connections, fade everything else
      cy.elements().removeClass('highlighted').removeClass('faded'); 
      node.addClass('highlighted');
      node.neighborhood().addClass('highlighted');
      cy.elements().difference(node.neighborhood().union(node)).addClass('faded');
      
      // If it's a tag node, try to trigger the corresponding tag filter
      if (nodeData.type === 'tag') {
        const tagName = nodeData.label;
        try {
          // Find and click the corresponding tag filter button
          const tagFilterBtn = Array.from(
            document.querySelectorAll('.tag-filter-btn')
          ).find(btn => btn.dataset.tag === tagName);
          
          if (tagFilterBtn) {
            tagFilterBtn.click();
          }
          
          // Scroll to the blog section
          const blogSection = document.querySelector('.blog-posts-section');
          if (blogSection) {
            blogSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
          }
        } catch (e) {
          console.log('Tag filter button not found or blog section not found');
        }
      }
    });

    cy.on('tap', function(e) {
      if (e.target === cy) { 
        if (nodeDetailsEl) nodeDetailsEl.classList.remove('active');
        cy.elements().removeClass('selected highlighted faded');
      }
    });

    if (closeDetailsBtn) {
      closeDetailsBtn.addEventListener('click', () => {
        if (nodeDetailsEl) nodeDetailsEl.classList.remove('active');
        cy.$(':selected').unselect();
        cy.elements().removeClass('highlighted faded');
      });
    }

    // Filter graph by node type
    const filterButtons = document.querySelectorAll('.graph-filter');
    filterButtons.forEach(button => {
      button.addEventListener('click', () => {
        filterButtons.forEach(btn => btn.classList.remove('active'));
        button.classList.add('active');
        const filter = button.dataset.filter;
        
        if (filter === 'all') {
          cy.elements().removeClass('faded highlighted filtered');
        } else if (filter === 'posts') {
          cy.elements().addClass('faded').removeClass('highlighted filtered');
          const postNodes = cy.nodes().filter(node => node.data('type') === 'post');
          postNodes.removeClass('faded').addClass('filtered');
        } else if (filter === 'tags') {
          cy.elements().addClass('faded').removeClass('highlighted filtered');
          const tagNodes = cy.nodes().filter(node => node.data('type') === 'tag');
          tagNodes.removeClass('faded').addClass('filtered');
        }
      });
    });

    // Zoom controls
    document.getElementById('zoom-in')?.addEventListener('click', () => cy.zoom(cy.zoom() * 1.2));
    document.getElementById('zoom-out')?.addEventListener('click', () => cy.zoom(cy.zoom() / 1.2));
    document.getElementById('reset-graph')?.addEventListener('click', () => {
        cy.fit(null, 30); 
        cy.elements().removeClass('faded highlighted filtered');
        const allFilterButton = document.querySelector('.graph-filter[data-filter="all"]');
        if (allFilterButton) allFilterButton.click(); 
    });

    // Add mouse wheel zoom controls
    cy.on('zoom', function() {
      if (cy.zoom() > 1.5) {
        cy.style().selector('node').style({ 'text-max-width': '150px', 'font-size': '12px' }).update();
      } else {
        cy.style().selector('node').style({ 'text-max-width': '120px', 'font-size': '10px' }).update();
      }
    });
    
    // Apply initial filter if specified
    if (initialFilter && initialFilter !== 'all') {
      const filterButton = document.querySelector(`.graph-filter[data-filter="${initialFilter}"]`);
      if (filterButton) {
        setTimeout(() => filterButton.click(), 500);
      }
    }
    
    // Connect search input if it exists
    const searchInput = document.getElementById('search-input');
    if (searchInput) {
      searchInput.addEventListener('input', e => {
        const term = e.target.value.toLowerCase();
        
        if (!term) {
          // Reset graph view if search is cleared
          cy.elements().removeClass('highlighted faded filtered');
          const allFilterBtn = document.querySelector('.graph-filter[data-filter="all"]');
          if (allFilterBtn) allFilterBtn.click();
          return;
        }
        
        // Reset previous filters
        cy.elements().addClass('faded').removeClass('highlighted filtered');
        
        // Find nodes that match the search term
        const matchingNodes = cy.nodes().filter(node => {
          const label = node.data('label').toLowerCase();
          return label.includes(term);
        });
        
        if (matchingNodes.length) {
          matchingNodes.removeClass('faded').addClass('highlighted');
          matchingNodes.connectedEdges().removeClass('faded');
        }
      });
    }
    
    // Connect tag filter buttons if they exist
    const tagFilterButtons = document.querySelectorAll('.tag-filter-btn');
    if (tagFilterButtons.length) {
      tagFilterButtons.forEach(btn => {
        btn.addEventListener('click', () => {
          const tagName = btn.dataset.tag;
          
          if (tagName === 'all') {
            // Reset graph view if "All" is selected
            cy.elements().removeClass('highlighted faded filtered');
            const allFilterBtn = document.querySelector('.graph-filter[data-filter="all"]');
            if (allFilterBtn) allFilterBtn.click();
            return;
          }
          
          // Find the tag node
          const tagNode = cy.nodes().filter(node => 
            node.data('type') === 'tag' && 
            node.data('label') === tagName
          );
          
          if (tagNode.length) {
            // Highlight this tag and connected posts in the graph
            cy.elements().addClass('faded').removeClass('highlighted filtered');
            tagNode.removeClass('faded').addClass('highlighted');
            
            // Get connected posts
            const connectedPosts = tagNode.neighborhood('node[type="post"]');
            connectedPosts.removeClass('faded').addClass('filtered');
            
            // Highlight connecting edges
            tagNode.connectedEdges().removeClass('faded').addClass('highlighted');
          }
        });
      });
    }
    
    // Dispatch graphReady event for external listeners
    document.dispatchEvent(new CustomEvent('graphReady', { detail: { cy } }));
  }

  // Initialize graph on DOMContentLoaded or if already loaded
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeGraph);
  } else {
    initializeGraph();
  }
</script>

<style>
  /* Graph Container Styles */
  .graph-container-wrapper { 
    position: relative; 
    width: 100%; 
    height: var(--graph-height, 50vh); 
    min-height: 400px; 
    max-height: 800px; 
    margin-bottom: 2rem;
  }
  
  .graph-container { 
    position: absolute; 
    top: 0; 
    left: 0; 
    width: 100%; 
    height: 100%; 
    border-radius: 12px; 
    overflow: hidden; 
    border: 1px solid var(--card-border); 
    background: rgba(15, 23, 42, 0.2); 
    backdrop-filter: blur(5px); 
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); 
  }
  
  #knowledge-graph { 
    width: 100%; 
    height: 100%; 
    z-index: 1; 
  }
  
  /* Loading Animation */
  .graph-loading { 
    position: absolute; 
    top: 0; 
    left: 0; 
    width: 100%; 
    height: 100%; 
    display: flex; 
    flex-direction: column; 
    align-items: center; 
    justify-content: center; 
    background: rgba(15, 23, 42, 0.7); 
    z-index: 10; 
    transition: opacity 0.5s ease, visibility 0.5s ease; 
    border-radius: 12px;
  }
  
  .graph-loading.hidden { 
    opacity: 0; 
    visibility: hidden; 
  }
  
  .loading-spinner { 
    position: relative; 
    width: 80px; 
    height: 80px; 
    margin-bottom: 1rem; 
  }
  
  .spinner-ring { 
    position: absolute; 
    width: 100%; 
    height: 100%; 
    border-radius: 50%; 
    border: 3px solid transparent; 
    border-top-color: var(--accent-primary); 
    animation: spin 1.5s linear infinite; 
  }
  
  .spinner-ring:nth-child(2) { 
    width: calc(100% - 15px); 
    height: calc(100% - 15px); 
    top: 7.5px; 
    left: 7.5px; 
    border-top-color: var(--accent-secondary); 
    animation-duration: 2s; 
    animation-direction: reverse; 
  }
  
  .spinner-ring:nth-child(3) { 
    width: calc(100% - 30px); 
    height: calc(100% - 30px); 
    top: 15px; 
    left: 15px; 
    border-top-color: var(--accent-tertiary); 
    animation-duration: 2.5s; 
  }
  
  @keyframes spin { 
    0% { transform: rotate(0deg); } 
    100% { transform: rotate(360deg); } 
  }
  
  .loading-text { 
    color: var(--text-primary); 
    font-family: var(--font-mono); 
    font-size: 1rem; 
    letter-spacing: 0.05em; 
  }
  
  /* Node Details Panel */
  .node-details { 
    position: absolute; 
    top: 20px; 
    right: 20px; 
    width: 300px; 
    background: var(--bg-secondary); 
    border: 1px solid var(--card-border); 
    border-radius: 10px; 
    padding: 1.5rem; 
    z-index: 5; 
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); 
    transform: translateX(120%); 
    transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); 
    opacity: 0; 
    backdrop-filter: blur(10px); 
  }
  
  .node-details.active { 
    transform: translateX(0); 
    opacity: 1; 
  }
  
  .node-details-header { 
    display: flex; 
    justify-content: space-between; 
    align-items: flex-start; 
    margin-bottom: 1rem; 
  }
  
  .node-title { 
    font-size: 1.2rem; 
    margin: 0; 
    color: var(--text-primary); 
    font-weight: 600; 
    line-height: 1.3; 
  }
  
  .close-button { 
    background: none; 
    border: none; 
    color: var(--text-secondary); 
    cursor: pointer; 
    padding: 5px; 
    border-radius: 50%; 
    display: flex; 
    align-items: center; 
    justify-content: center; 
    transition: all 0.2s ease; 
  }
  
  .close-button:hover { 
    color: var(--text-primary); 
    background: rgba(255, 255, 255, 0.1); 
  }
  
  .node-type {
    margin-bottom: 1rem;
  }
  
  .type-value {
    display: inline-block;
    padding: 0.25rem 0.75rem;
    border-radius: 20px;
    font-size: 0.85rem;
    font-weight: 500;
  }
  
  .post-type {
    background-color: rgba(59, 130, 246, 0.15);
    color: #3B82F6;
  }
  
  .tag-type {
    background-color: rgba(16, 185, 129, 0.15);
    color: #10B981;
  }
  
  .category-type {
    background-color: rgba(139, 92, 246, 0.15);
    color: #8B5CF6;
  }
  
  .node-category, .node-tags, .node-connections { 
    margin-bottom: 1.25rem; 
  }
  
  .category-label, .tags-label, .connections-label { 
    display: block; 
    color: var(--text-secondary); 
    font-size: 0.85rem; 
    margin-bottom: 0.5rem; 
    font-family: var(--font-mono); 
  }
  
  .category-value { 
    display: inline-block; 
    padding: 0.25rem 0.75rem; 
    border-radius: 20px; 
    font-size: 0.85rem; 
    font-family: var(--font-mono); 
  }
  
  .tags-container { 
    display: flex; 
    flex-wrap: wrap; 
    gap: 0.5rem; 
  }
  
  .tag { 
    background: rgba(16, 185, 129, 0.1); 
    color: #10B981; 
    padding: 0.25rem 0.5rem; 
    border-radius: 4px; 
    font-size: 0.75rem; 
    cursor: pointer;
    transition: all 0.2s ease;
  }
  
  .tag:hover {
    background: rgba(16, 185, 129, 0.2);
    transform: translateY(-2px);
  }
  
  .no-tags { 
    color: var(--text-tertiary); 
    font-style: italic; 
    font-size: 0.8rem; 
  }
  
  .connections-list { 
    padding-left: 0; 
    list-style: none; 
    margin: 0; 
    max-height: 150px; 
    overflow-y: auto; 
  }
  
  .connections-list li { 
    color: var(--text-secondary); 
    font-size: 0.9rem; 
    margin-bottom: 0.5rem; 
  }
  
  .connections-list a { 
    color: var(--accent-primary); 
    text-decoration: none; 
    transition: color 0.2s ease; 
  }
  
  .connections-list a:hover { 
    color: var(--accent-secondary); 
    text-decoration: underline; 
  }
  
  .node-link { 
    display: block; 
    background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); 
    color: var(--bg-primary); 
    font-weight: 500; 
    padding: 0.6rem 1.25rem; 
    border-radius: 6px; 
    text-decoration: none; 
    text-align: center; 
    transition: all 0.3s ease; 
    box-shadow: 0 4px 10px rgba(6, 182, 212, 0.2); 
    margin-top: 1rem; 
  }
  
  .node-link:hover { 
    transform: translateY(-2px); 
    box-shadow: 0 6px 15px rgba(6, 182, 212, 0.3); 
  }
  
  /* Graph Controls */
  .graph-controls { 
    position: absolute; 
    bottom: 20px; 
    left: 50%; 
    transform: translateX(-50%); 
    display: flex; 
    flex-direction: column; 
    gap: 1rem; 
    z-index: 5; 
  }
  
  .graph-filters { 
    display: flex; 
    flex-wrap: wrap; 
    gap: 0.5rem; 
    justify-content: center; 
    padding: 0.75rem; 
    background: rgba(15, 23, 42, 0.7); 
    backdrop-filter: blur(10px); 
    border-radius: 30px; 
    border: 1px solid var(--border-primary); 
  }
  
  .graph-filter { 
    background: rgba(226, 232, 240, 0.05); 
    color: var(--text-secondary); 
    border: 1px solid var(--border-secondary); 
    padding: 0.4rem 0.8rem; 
    border-radius: 30px; 
    font-size: 0.8rem; 
    cursor: pointer; 
    transition: all 0.2s ease; 
    font-family: var(--font-mono); 
    position: relative; 
    overflow: hidden; 
  }
  
  .graph-filter::before { 
    content: ''; 
    position: absolute; 
    top: 0; 
    left: 0; 
    width: 0; 
    height: 100%; 
    background: var(--filter-color, 'transparent'); 
    opacity: 0.15; 
    transition: width 0.3s ease; 
  }
  
  .graph-filter:hover::before { 
    width: 100%; 
  }
  
  .graph-filter:hover { 
    color: var(--text-primary); 
    border-color: var(--filter-color, rgba(56, 189, 248, 0.4)); 
    transform: translateY(-2px); 
  }
  
  .graph-filter.active { 
    background-color: var(--filter-color, var(--accent-primary)); 
    color: var(--bg-primary); 
    border-color: var(--filter-color, var(--accent-primary)); 
    font-weight: 600; 
  }
  
  .graph-filter.active::before { 
    width: 100%; 
    opacity: 0.2; 
  }
  
  .graph-actions { 
    display: flex; 
    justify-content: center; 
    gap: 0.75rem; 
  }
  
  .graph-action { 
    width: 36px; 
    height: 36px; 
    border-radius: 50%; 
    background: var(--bg-secondary); 
    border: 1px solid var(--border-primary); 
    color: var(--text-secondary); 
    display: flex; 
    align-items: center; 
    justify-content: center; 
    cursor: pointer; 
    transition: all 0.2s ease; 
  }
  
  .graph-action:hover { 
    color: var(--text-primary); 
    background: var(--bg-tertiary); 
    transform: translateY(-2px); 
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 
  }
  
  /* Graph Instructions */
  .graph-instructions {
    margin-bottom: 1rem;
    width: 100%;
  }
  
  .instructions-details {
    background: rgba(15, 23, 42, 0.3);
    border-radius: 8px;
    border: 1px solid var(--border-primary);
    overflow: hidden;
    transition: all 0.3s ease;
  }
  
  .instructions-details[open] {
    padding-bottom: 1rem;
  }
  
  .instructions-summary {
    padding: 0.75rem 1rem;
    cursor: pointer;
    color: var(--text-primary);
    font-weight: 500;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  
  .instructions-summary::before {
    content: "ℹ️";
    font-size: 1rem;
  }
  
  .instructions-content {
    padding: 0 1rem;
    color: var(--text-secondary);
  }
  
  .instructions-content ul {
    padding-left: 1.5rem;
    margin: 0.5rem 0 1rem;
  }
  
  .node-example {
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-right: 0.25rem;
    vertical-align: middle;
  }
  
  .post-node {
    background-color: #3B82F6;
    border-radius: 50%;
  }
  
  .tag-node {
    background-color: #10B981;
    transform: rotate(45deg);
  }
  
  /* Media Queries */
  @media screen and (max-width: 768px) {
    .node-details { 
      width: 85%; 
      max-width: 300px; 
      left: 50%; 
      right: auto; 
      transform: translate(-50%, 120%); 
      bottom: 20px; 
      top: auto; 
    }
    
    .node-details.active { 
      transform: translate(-50%, 0); 
    }
    
    .graph-instructions {
      margin-bottom: 0.5rem;
    }
    
    .graph-controls { 
      bottom: 10px; 
    }
    
    .graph-filters {
      padding: 0.5rem;
    }
    
    .graph-filter {
      padding: 0.3rem 0.6rem;
      font-size: 0.75rem;
    }
  }