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

// Assuming Cytoscape is loaded via CDN in BaseLayout or globally
// If not, you might need: import cytoscape from 'cytoscape';

export interface GraphNode {
  id: string;
  label: string;
  category?: string;
  tags?: string[];
  url?: string; // Added URL for linking
}

export interface GraphEdge {
  source: string;
  target: string;
  strength?: number;
}

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

interface Props {
  graphData: GraphData;
  height?: string; // e.g., '600px'
}

const { graphData, height = "60vh" } = Astro.props;

// Generate colors based on categories for nodes
const uniqueCategories = [...new Set(graphData.nodes.map(node => node.category || 'Uncategorized'))];
const categoryColors = {};
const predefinedColors = { /* Colors from previous step */
  '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'
};
uniqueCategories.forEach((category, index) => {
  if (predefinedColors[category]) {
    categoryColors[category] = predefinedColors[category];
  } else {
    const hue = (index * 137.5) % 360;
    categoryColors[category] = `hsl(${hue}, 70%, 60%)`;
  }
});

// Calculate node sizes
const nodeSizes = {};
const minSize = 20; const maxSize = 40;
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;
  const normalizedSize = maxDegree === 0 ? 0.5 : degree / maxDegree;
  nodeSizes[node.id] = minSize + normalizedSize * (maxSize - minSize);
});
---

<div class="graph-wrapper" style={`--graph-height: ${height};`}>
  {/* 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-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="_blank" rel="noopener noreferrer">Read Article</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 Topics</button>
       {uniqueCategories.map(category => (
         <button class="graph-filter" data-filter={category} style={`--filter-color: ${categoryColors[category]};`}>{category}</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>

  {/* Graph Legend */}
  <details class="graph-legend">
    <summary class="legend-title">Legend</summary>
    <div class="legend-items">
      {uniqueCategories.map(category => (
        <div class="legend-item" data-category={category}>
          <span class="legend-color" style={`background-color: ${categoryColors[category]};`}></span>
          <span class="legend-label">{category}</span>
        </div>
      ))}
    </div>
  </details>

</div>

{/* Include Cytoscape via CDN - Ensure this is loaded, perhaps in BaseLayout */}
{/* <script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js"></script> */}

<script define:vars={{ graphData, categoryColors, nodeSizes }}>
  // 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 (e.g., via CDN in BaseLayout).");
      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 = [];
    graphData.nodes.forEach(node => {
      elements.push({
        data: {
          id: node.id,
          label: node.label,
          category: node.category || 'Uncategorized',
          tags: node.tags || [],
          size: nodeSizes[node.id] || 25, 
          color: categoryColors[node.category || 'Uncategorized'] || '#A0AEC0', 
          url: `/posts/${node.id}/` 
        }
      });
    });
    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, 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: [ /* Styles from your snippet */
         { 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 } },
         { selector: 'edge', style: { 'width': 'mapData(weight, 1, 10, 1, 4)', 'line-color': 'rgba(226, 232, 240, 0.2)', 'curve-style': 'bezier', 'opacity': 0.6, 'z-index': 1 } },
         { 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 } }
      ],
      layout: { name: 'cose', idealEdgeLength: 100, nodeOverlap: 20, refresh: 20, fit: true, padding: 30, randomize: false, componentSpacing: 100, nodeRepulsion: 400000, edgeElasticity: 100, nestingFactor: 5, gravity: 80, numIter: 1000, initialTemp: 200, coolingFactor: 0.95, minTemp: 1.0 },
      zoom: 1, minZoom: 0.1, maxZoom: 4, 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'; // Use graphContainer
    });

    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'; // Use graphContainer
    });

    cy.on('tap', 'node', function(e) {
      const node = e.target;
      const nodeData = node.data();
      
      if (nodeDetailsEl) {
          document.getElementById('node-title').textContent = nodeData.label;
          const categoryEl = document.getElementById('node-category').querySelector('.category-value');
          categoryEl.textContent = nodeData.category;
          // Use categoryColors map correctly
          const catColor = categoryColors[nodeData.category] || 'var(--text-secondary)'; 
          categoryEl.style.backgroundColor = `${catColor}33`; // Add alpha
          categoryEl.style.color = catColor;
          
          const tagsContainer = document.getElementById('node-tags').querySelector('.tags-container');
          tagsContainer.innerHTML = '';
          if (nodeData.tags && nodeData.tags.length > 0) {
            nodeData.tags.forEach(tag => {
              const tagEl = document.createElement('span');
              tagEl.className = 'tag';
              tagEl.textContent = tag;
              tagsContainer.appendChild(tagEl);
            });
          } else {
            tagsContainer.innerHTML = '<span class="no-tags">No tags</span>';
          }
          
          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) { // Check if node exists
                    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>';
          }
          
          document.getElementById('node-link').href = nodeData.url;
          nodeDetailsEl.classList.add('active');
      }
      
      cy.elements().removeClass('highlighted').removeClass('faded'); 
      node.addClass('highlighted');
      node.neighborhood().addClass('highlighted');
      cy.elements().difference(node.neighborhood().union(node)).addClass('faded');
    });

    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');
      });
    }

    // Category filtering
    const filterButtons = document.querySelectorAll('.graph-filter');
    filterButtons.forEach(button => {
      button.addEventListener('click', () => {
        filterButtons.forEach(btn => btn.classList.remove('active'));
        button.classList.add('active');
        const category = button.dataset.filter;
        
        if (category === 'all') {
          cy.elements().removeClass('faded highlighted filtered');
        } else {
          cy.elements().addClass('faded').removeClass('highlighted filtered');
          const selectedNodes = cy.nodes().filter(node => node.data('category') === category);
          selectedNodes.removeClass('faded').addClass('filtered');
          selectedNodes.connectedEdges().removeClass('faded'); 
        }
      });
    });

    // Legend item interactions
    const legendItems = document.querySelectorAll('.legend-item');
    legendItems.forEach(item => {
      item.addEventListener('click', () => {
        const category = item.dataset.category;
        const filterButton = document.querySelector(`.graph-filter[data-filter="${category}"]`);
        filterButton?.click(); 
      });
    });

    // 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');
        document.querySelector('.graph-filter[data-filter="all"]')?.click(); 
    });

    // Add mouse wheel zoom controls (already present in original script)
    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();
      }
    });
    
    // Dispatch graphReady event
    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>
  /* Styles from user snippet */
  .graph-wrapper { position: relative; width: 100%; height: var(--graph-height, 60vh); min-height: 500px; max-height: 800px; }
  .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; }
  .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; }
  .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 { 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: 1.25rem; }
  .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-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(226, 232, 240, 0.05); color: var(--text-secondary); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-family: var(--font-mono); }
  .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 { 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-legend { position: absolute; top: 20px; left: 20px; background: rgba(15, 23, 42, 0.7); border: 1px solid var(--card-border); border-radius: 10px; padding: 1rem; z-index: 5; max-width: 200px; backdrop-filter: blur(10px); }
  .legend-title { font-size: 0.9rem; color: var(--text-primary); margin-bottom: 0.75rem; font-weight: 600; font-family: var(--font-mono); cursor: pointer; }
  .legend-items { display: flex; flex-direction: column; gap: 0.5rem; max-height: 200px; overflow-y: auto; }
  .graph-legend:not([open]) .legend-items { display: none; } 
  .legend-item { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; transition: all 0.2s ease; padding: 0.25rem 0.5rem; border-radius: 4px; }
  .legend-item:hover { background: rgba(255, 255, 255, 0.05); }
  .legend-color { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
  .legend-label { font-size: 0.8rem; color: var(--text-secondary); }
  @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-legend { transform: translateX(-120%); transition: transform 0.3s ease; max-height: calc(100% - 40px); overflow-y: auto; }
    .graph-legend[open] { transform: translateX(0); } 
    .graph-controls { bottom: 10px; }
  }
</style>